一:属性动画简介
Android 属性动画是API 11引入的,更在的版本可以使用Nineoldandroids来实现,Nineoldandroids官方网站http://nineoldandroids.com。
Nineoldandroids对属性动画做了兼容,在API 11之前的版本通过代理View动画来实现,因此在API 11之前的版本,本质还是View动画。Nineoldandroids的功能和系统原生的android,animation.*中类的功能用法完全一致。比较常用的几个动画类是:ValueAnimator,ObjectAnimator和AnimatorSet,其中ObjectAnimator继承自ValueAnimator,AnimatorSet是动画集合。
先看几个小列子:
1.
ObjectAnimator.ofInt(myObjec,"width",500).setDuration(5000).start();
上面这句代码的作用就是使用ObjectAnimator属性动画来让myObjec这个对象的width属性在5s变为500px。
2.
ObjectAnimator.ofFloat(myObjec,"translationY", -myObjec.getHeight()).start();
上面这段代码的作用就是改变对象myObjec的translationY属性,让其沿着Y轴向上平移一段距离(myObjec的高度)。
3.
AnimatorSet set = new AnimatorSet();
set.playTogether(
ObjectAnimator.ofFloat(myObjec, "alpha", 1,0.5f,1),
ObjectAnimator.ofFloat(myObjec, "rotationX", 0,180),
ObjectAnimator.ofFloat(myObjec, "rotationY", 0,270),
ObjectAnimator.ofFloat(myObjec, "translationY", 0,100),
ObjectAnimator.ofFloat(myObjec, "translationX", 0,100),
ObjectAnimator.ofFloat(myObjec, "rotation", 0,-270),
ObjectAnimator.ofFloat(myObjec, "scaleX", 1,1.2f),
ObjectAnimator.ofFloat(myObjec, "scaleY", 1,0.5f)
);
set.setDuration(5000).start();
上面代码的作用在5s内对View做了透明度,旋转,平移,缩放动画。
从上面3个小例子不难看出,对一个view使用属性动画的代码还是比较容易理解的,当然,属性动画也可以通过XML来定义。
需要定义在res/animator目录下面。其大概样式如下:
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android" android:ordering="together" >
<objectAnimator android:duration="1000" android:propertyName="scaleX" android:valueFrom="1" android:valueTo="0.5" >
</objectAnimator>
<animator android:duration="1000" android:valueFrom="1" android:valueTo="0.5" >
</animator>
</set>
下面对标签说明下:
objectAnimator标签属性:
android:propertyName——表示属性动画的作用对象的属性名称;
android:duration——动画的时长;
android:valueFrom——表示属性的起始值;
android:valueTo——表示属性的结束值;
android:startOffset——动画的延迟时间;
android:repeatCount——动画的重复次数(默认值0,-1表示无限循环);
android:repeatMode——动画的重复模式(“repeat”表示连续重复,“reverse”表示逆向重复。);
android:valueType——表示android:propertyName所指属性的类型,有“intType”和“floatType”两个可选,分别表示属性的类型为整形和浮点型。(如果android:propertyName所指的属性为颜色,那么不需要指定android:valueType,系统会自动做处理)。
使用XML定义的属性动画非常简单,代码大概如下:
Animator anim = AnimatorInflater.loadAnimator(this, R.animator.scale);
anim.setTarget(mMv);
anim.start();
二:属性动画的监听器
属性动画提供了监听器用于监听动画的播放过程,主要有AnimatorUpdateListener和AnimatorListener。
AnimatorListener的定义如下:
public static Interface AnimatorListener{
void onAnimationStart(Animator arg0);
void onAnimationRepeat(Animator arg0);
void onAnimationEnd(Animator arg0);
void onAnimationCancel(Animator arg0);
}
从定义中可以看出它可以监听动画的开始、结束、取消以及重复播放。使用起来也很简单因为系统提供了AnimatorListener的适配器。使用代码如下:
ValueAnimator valueAnimator = ValueAnimator.ofInt(1, 100);
valueAnimator.addListener(new AnimatorListener() {
@Override
public void onAnimationStart(Animator arg0) {
// TODO Auto-generated method stub
}
@Override
public void onAnimationRepeat(Animator arg0) {
// TODO Auto-generated method stub
}
@Override
public void onAnimationEnd(Animator arg0) {
// TODO Auto-generated method stub
}
@Override
public void onAnimationCancel(Animator arg0) {
// TODO Auto-generated method stub
}
});
三:高级使用
首先先分析下属性动画的原理:
属性动画要求动画作用的对象提供该属性的set和get方法,属性动画根据外界传递的初始值和最终值,以动画的效果多次去调用set方法,没次传递给set方法的值都不一样,确切说是随着时间的推移,所传递的值越来越接近最终的值。那么想要给object的属性A做动画,那么必须要满足以下两个条件:
(1)object必须提供setA方法,如果动画开始的时候没有传递初始值,那么还要提供getA方法,因为系统要调用getA方法去获取初始值。
(2)object的setA对属性A所做的改变必须能够通过某种方式反映出来,比如会带来UI的变换之类的否则动画无效果。
例如,想给一个Button增加一个动画,使他的宽在5s内增加到500px。从Button的源码可以知道Button继承自TextView其getWidth获取的是其宽度,但是其setWidth是TextView和其子类的专属方法,它的作用不是设置View的宽度,而是设置TextView的最大宽度和最小宽度。其实就是,TextView的宽度对应XML中的android:layout_width属性,而TextView还有一个属性android:width,这个属性就对应了setWidth方法。所以通过setWidth无法改变控件的宽度。官方文档对于这种情况给出了3中解决方法:
这里发现第一种方案可行性极低,因为我们通常是没有修改这些组件的权限的。下面用代码来实现以下第二中方案,先看代码:
private void performAnimate(){
ViewWrapper wrapper = new ViewWrapper(button,button.getWidth());
ObjectAnimator.ofInt(wrapper,"width",500).setDuration(5000).start();
}
private static class ViewWrapper{
private View mTarget;
private int mWidth;
public ViewWrapper(View target,int width){
mTarget = target;
mWidth = width;
}
public int getWidth(){
//return mTarget.getLayoutParams().width;
return mWidth;
}
public void setWidth(int width){
mTarget.getLayoutParams().width = width;
mTarget.requestLayout();
}
}
从上面代码中不难看出我们首先是定义了一个类(ViewWrapper)来包装原始对象(Button),然后通过ViewWrapper的get和set方法来改变原对象的宽度。
对于第三种解决方案,同样先看代码:
private void performAnimate(final View target, final int start, final int end) {
ValueAnimator valueAnimator = ValueAnimator.ofInt(1, 100);
valueAnimator.addUpdateListener(new AnimatorUpdateListener() {
// 持有一个IntEvaluator对象,方便下面估值的时候使用
private IntEvaluator mEvaluator = new IntEvaluator();
@Override
public void onAnimationUpdate(ValueAnimator animator) {
// 获得当前动画的进度值,整型,1-100之间
int currentValue = (Integer) animator.getAnimatedValue();
Log.d(TAG, "current value: " + currentValue);
// 获得当前进度占整个动画过程的比例,浮点型,0-1之间
float fraction = animator.getAnimatedFraction();
// 直接调用整型估值器通过比例计算出宽度,然后再设给Button
target.getLayoutParams().width = mEvaluator.evaluate(fraction, start, end);
target.requestLayout();
}
});
valueAnimator.setDuration(5000).start();
}
上面的代码,会在5s内将一个数从1变到100,然后动画的每一帧都会调用onAnimationUpdate方法,在这个方法内可以获取当前的值和当前值所占的比例,就可以计算出此刻的宽度是多少,例如时间过了一半,那么当前值应该是50,比例是0.5,假设Button的初始宽度是100px,最终宽度是500px,那么此时的Button应该增加了(500-100)*0.5=200,那么当前的宽度应该是100+200=300。
到此属性动画大概讲述了一遍。