介绍
在属性在没有出现之前,在使用ViewAnimation的时候,相信多知道它有以下这些缺点:
1.ViewAnimation系统受到限制,它仅将View对象的几个方面暴露给动画,只能够实现平移、缩放、旋转和透明度这四种动画操作,如果我们希望可以对View的背景色、或者是非View对象进行动态地改变,只能自己去实现了。
2.ViewAnimation还有一个缺点就是,就是它只修改View的绘制位置,而不是实际View的本身。例如,如果使用动画将按钮从左移动到屏幕右边,按钮正确绘制,但当你单击按钮时事件却不会触发,点击左边按键原来的位置,事件却触发了。因此,要处理这些问题,也要自己去实现逻辑。
因为以上的需求,在Android 3.0版本开始,系统给我们提供了一种全新的动画模式,属性动画(property animation)。
使用属性动画系统,不但可以实现ViewAnimation一样的动画效果,而且也不会有以上缺点,即属性动画可以对任何对象(视图和非视图)的任何属性进行动画处理,并且对象本身实际上进行了修改。属性动画系统在执行动画的过程中也更为强大,在大部分场合完全可以替换ViewAnimation
那么,属性动画那么牛逼,是不是就学习属性动画就可以了?
我觉得,属性动画是很好的补充,但并非是替代作用。有些场景目前还必须使用视图动画,比如ListView的item动画;而且,我觉得属性动画有个麻烦的地方就是,对于清除动画的效果,没有直接公用的方法,需要自己写(也可能我不知道)。
官方文档是这样推荐:
视图动画系统需要较少的时间来设置,并且需要较少的代码来写入。如果视图动画完成了您需要执行的所有操作,或者如果现有代码已经按照您想要的方式工作,则不需要使用属性动画系统。比如有些用例,在不同情况下使用2种动画系统,那也可能是有意义的。
属性动画的API
在属性动画中,很多api的方法与视图动画的方法很类似,有些是公用的,比如,在视图动画系统中,已经定义了许多插值在android.view.animation,同样,属性动画也可以使用。
属性动画Animator类,提供子类给我们使用,可以分为以下3类:
类 | 说明 |
---|---|
ValueAnimator | 主要提供了属性动画的时间引擎,并且以动画的方式计算各种属性值。它定义了属性动画绝大部分的核心功能,包括:计算各帧的相关属性值、动画是否重复、负责处理更新时间、可以定义evaluate类型控制计算规则等等。属性动画主要有2方面组成:①计算各帧的相关属性值;②为指定的对象设置计算后的值。ValueAnimator只负责第一方面,因此使用时必须根据ValueAnimator计算并监听值的更新,来更新对象的相关属性值。 |
ObjectAnimator | 它是ValueAnimator的直接子类,允许指定目标对象和相关属性值执行动画。它可以直接在目标对象使用动画,使用相对比较简单,因此最为常用。但有时,却需要使用ValueAnimator,因为,ObjectAnimator设置目标的属性值有一定的限制,目标对象需要拥有特定的方法属性。 |
AnimatorSet | 用于组合多个动画,类似视图动画的AnimationSet。同样的,可以指定多个Animator的播放顺序,设置延迟时间等。 |
ValueAnimator
ValueAnimator 可以静态的调用其工厂模式的方法返回想要的ValueAnimator 实例,如图:
其使用也较为简单,多是有套路的,把他们总结如下:
- 调用ValueAnimator 的静态方法创建ValueAnimator 实例,并设置需要变化的相关属性
- 调用ValueAnimator 的setXxx( )方法设置动画的持续时间、插值方式、重复次数等等,与视图动画大部分一样样;
- 调用ValueAnimator的start( )方法启动动画
- (可选)为ValueAnimator设置AnimatorUpdateListener监听,监听属性值的变化,并设置到目标对象
有了以上的套路,那么就可以按部就班了。。。
1.ofFloat(float... values)
参数:可变参数,若设置一个,那么默认范围就是0~values[0];
使用:
ValueAnimator valueAnimator = ValueAnimator.ofFloat(-100, 100);//设置需要变化的属性值范围
valueAnimator.setDuration(2000);//设置动画时间
valueAnimator.setRepeatCount(ValueAnimator.INFINITE);//设置动画一直重复
valueAnimator.setRepeatMode(ValueAnimator.REVERSE);//设置动画重复的模式
valueAnimator.start();//开始动画
//注册监听,监听属性值的变化,并设置给目标对象
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener()
{
@Override
public void onAnimationUpdate(ValueAnimator animation)
{
float value = (float) animation.getAnimatedValue();
mTarget.setTranslationX(value);
}
});
2.ofInt(int... values)
它的用法完全和ofFloat()类似,只是属性值的类型不同而已,这里不再重复。
3.ofObject(TypeEvaluator evaluator, Object... values)
参数:第一个参数是估值器,即控制属性值的变化规则,想要详细了解估值器可先往下看; 第二个,与上面的一样的,可变参数,只不过对象是Object,也可以自定义对象,范围更广。
使用:只要将上面的返回的实例改一下即可:
valueAnimator = ValueAnimator.ofObject(new FloatEvaluator(), -100f, 100f);
注意的是,后面2个参数是Float类型的,那么估算值的类型就不能用IntEvaluator。当然,我们也可以自定义估算值的类型和对象。
4.ofArgb(int... values)
参数:可变参数,其参数为颜色值,即16进制的int值,如:0xff000000
使用:
objectAnimator = ObjectAnimator.ofArgb(BLCAK, RED, BLUE)
该方法是从API Level 21开始,如果版本太低需要加判断。该方法接收一些代表颜色的int值,其内部使用了ArgbEvaluator估值器,可以用该方法实现将一个颜色动画渐变到另一个颜色,并且从监听中可以不断获取中间动画产生的颜色值。
那么,你可能有这样的问题:既然传入的还是int值,那么直接用ofInt()方法不就行了吗?干嘛还要新增一个ofArgb()方法呢?
你可以试试,如果使用ofInt( ),虽然可以完成颜色动画渐变,但效果会一直闪动。如果使用ofArgb(),可以从源码看到:
public static ValueAnimator ofArgb(int... values) {
ValueAnimator anim = new ValueAnimator();
anim.setIntValues(values);
anim.setEvaluator(ArgbEvaluator.getInstance());
return anim;
}
其实也是先调用ofInt()方法,只不过后面设置了颜色的估值器ArgbEvaluator。既然这样,那么就有办法消除刚才一直闪动的问题了。是的,只要设置了估值器,和ofArgb()效果一样。
那ArgbEvaluator里面到底设置了什么呢?通过源码我们知道:
一个颜色的int值包含四个字节,在Android中第一个字节代表Alpha分量,第二个字节代表Red分量,第三个字节代表Green分量,第四个字节代表Blue分量,且我们常用16进制表示颜色,这样看起来更明显易懂一些,比如int值0xffff0000表示的红色,0xff00ff00表示的是绿色,最前面的ff表示的是Alpha。ofArgb方法会通过ArgbEvaluator将颜色拆分成四个分量,然后分别对各个分量进行动画计算,然后将四个计算完的分量再重新组合成一个表示颜色的int值,这就是ofArgb方法的工作原理。所以,如果使用ofInt()不设置ArgbEvaluator,那么会将整个颜色值当做一个单独的整数来计算,这样子就会导致闪烁了。
好了,ValueAnimator 的使用就是这么简单,但实际中应用相对比较少。用的最多是ObjectAnimator,下面来介绍它。
ObjectAnimator
ObjectAnimator是最常用的了,它是ValueAnimator 的直接子类,那么就继承ValueAnimator 的功能,而且它可以直接将属性值设置给目标对象,这使得任何对象变得更加容易,因为不再需要实现ValueAnimator.AnimatorUpdateListener,动画属性会自动更新。
ObjectAnimator中,通过静态方法返回ObjectAnimator实例的方法有:
这么多,有点吓人。但平时用的多也就几个,只要会使用ValueAnimator ,这个也很简单,只是参数不一样。拿一个来举例子。
ofInt(Object target, String propertyName, int... values)
参数:target:目标对象 ; propertyName 属性名 ; values 属性范围值
使用:
objectAnimator = ObjectAnimator.ofInt(mTarget, "textColor", BLCAK, RED, BLUE);//实例化,设置文本颜色值从BLCAK~RED~BLUE变化
objectAnimator.setDuration(2000);//设置动画持续时间
objectAnimator.setEvaluator(new ArgbEvaluator());//设置估算值(前面说过,使用ofInt()需要添加这行代码)
objectAnimator.setRepeatCount(ValueAnimator.INFINITE);//设置一直重复
objectAnimator.setRepeatMode(ValueAnimator.REVERSE);//设置一直重复模式
objectAnimator.start();//开始动画
第一个参数:就是需要设置动画的控件对象mTarget; 第2个参数:设置属性名为“textColor”,文本的颜色;后面几个参数是属性计算值;最后设置一些和上面一样的属性就可以了。
发现没有,这里不用注册监听就直接可以将计算的值设置给目标对象,简单易用。
运行之后,我们可以看到字体的颜色一直在变化,是不是有点酷,但你要是放在视图动画中就不能直接实现。
其他方法使用都一样,这里不再详细介绍。相信大家更想知道的问题是这个:
问题:第2个参数propertyName ,到底可以设置哪些值呢?
答案是:任意值!是的,属性动画不仅单独是针对视图,它可以支持任何对象,那么就可以设置任何值,没毛病!下面进行介绍:
首先,我们前面说到,属性动画可以替换(不是替代)视图动画实现相同的效果,那么改参数肯定有透明度、旋转、缩放等,如下:
- translationX和translationY:控制View由它的布局容器设置其左侧和顶部坐标的增量。
- rotation,rotationX和rotationY:控制View在容器2D(旋转rotation绕枢轴点属性)和3D旋转,可以支持3D效果噢。
- scaleX和scaleY:这些属性控制围绕其轴心点视图的2D缩放。
- alpha:代表在查看Alpha透明度。此值是1(不透明)通过默认,以0表示全透明度的值(不可见)。
- ......
这里拿旋转来示范,他可以支持2D、3D的旋转,其他可以自行测试:
那么问题又来了,既然可以是任意值,是否随意拿一个就可以呢?非也!官方文档的解释是:
要使ObjectAnimator更新属性正确,您必须执行以下操作:
● 您要动画化的对象属性必须具有setter函数(以骆驼的形式) set<propertyName>()。因为在ObjectAnimator 动画过程中自动更新属性,所以必须能够使用此setter方法访问该属性。例如,如果属性名称是foo,则需要有一个setFoo()方法。如果此setter方法不存在,您有三个选项:
○ 如果您有权这样做,请将setter方法添加到类中。
○ 使用您有权更改的包装类,并使该包装器使用有效的setter方法接收该值并将其转发到原始对象。
○ 使用ValueAnimator来代替。
● 如果values...在一种ObjectAnimator工厂方法中仅为参数指定一个值,则假设它是动画的结束值。因此,您正在动画的对象属性必须具有用于获取动画起始值的getter函数。getter方法必须采用的形式get<propertyName>()。例如,如果属性名称是 foo,则需要有一个getFoo()方法。
● 您所动画的属性的getter(如果需要)和setter方法必须与您指定的起始和结束值相同的类型运行ObjectAnimator。例如,您必须拥有 targetObject.setPropName(float)并targetObject.getPropName(float) 构建以下内容ObjectAnimator:
ObjectAnimator.ofFloat(targetObject, "propName", 1f)
● 根据您要动画的属性或对象,您可能需要invalidate()在“视图”上调用该方法,强制屏幕使用更新的动画值重新绘制自己。你在onAnimationUpdate() 回调中这样做 。例如,当Drawable对象重新绘制时,动画化Drawable对象的颜色属性只会导致更新屏幕。所有的属性设置,如 setAlpha()和setTranslationX() 正确无效视图,所以你不需要调用这些方法与新的值时无效视图。
总结出来就是:该对象的属性必须有setter函数,getter函数则需看设置属性计算值的参数,1个则必须,2个可以没有。
AnimatorSet
在许多情况下,当播放依赖于另一个动画启动或完成的动画,或者多个动画一起播放的时候,则需要将动画组合起来。Android系统允许将动画捆绑在一起AnimatorSet,以便可以指定是同时,顺序还是在指定的延迟之后启动动画,也可以将AnimatorSet对象再嵌套AnimatorSet中。
AnimatorSet这个类主要提供了三个播放方法:
playSequentially():表示多个动画按顺序执,它主要有两种形式的参数playSequentially(Animator… items)和playSequentially(List <Animator> animator);一个是可变参数,另一个是动画集合。
playTogether():表示几个动画同时执行,它接收的参数类型也有2种,与playSequentially()一致。
play():play(Animator anim),参数是一个Animator动画实例,调用它之后会返回一个AnimatorSet.Builder的实例,AnimatorSet.Builder中包括以下四个方法:
- after(Animator anim) 将现有动画插入到传入的动画之后执行
- after(long delay) 将现有动画延迟指定毫秒后执行
- before(Animator anim) 将现有动画插入到传入的动画之前执行
- with(Animator anim) 将现有动画和传入的动画同时执行
使用:
ObjectAnimator translationX = ObjectAnimator.ofFloat(mTarget, "translationX", 400f, 0f);
ObjectAnimator alpha = ObjectAnimator.ofFloat(mTarget, "alpha", 1f, 0f, 1f);
ObjectAnimator rotation = ObjectAnimator.ofFloat(mTarget, "rotation", 0f, 360f);
AnimatorSet animSet = new AnimatorSet();
animSet.play(rotation).with(alpha).after(translationX);
animSet.setDuration(4000);
animSet.start();
Animation Listeners
onAnimationStart() - 动画启动时调用。
onAnimationEnd() - 当动画结束时调用。
onAnimationRepeat() - 当动画重演调用。
onAnimationCancel()-当动画被取消调用。取消的动画还呼吁onAnimationEnd(),不管他们是如何结束。
//实现 AnimatorListener 接口
objectAnimator.addListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animation) {
}
@Override
public void onAnimationEnd(Animator animation) {
}
@Override
public void onAnimationCancel(Animator animation) {
}
@Override
public void onAnimationRepeat(Animator animation) {
}
});
//实现 AnimatorListenerAdapter 抽象类
objectAnimator.addListener(new AnimatorListenerAdapter() {
//可以自行覆盖未实现方法
});
onAnimationUpdate() 每一帧动画都会回调,监听属性计算值的变化,可以通过getAnimatedValue()的方法来查询值的变化。
在XML使用Animator
- <animator> 对应代码中的ValueAnimator
- <objectAnimator> 对应代码中的ObjectAnimator
- <set> 对应代码中的AnimatorSet
<set
android:ordering=["together" | "sequentially"]> //指定此集中动画的播放顺序,其值可以是:together(依次播放此集合中的动画)、sequentially(在同一时间播放动画)。
<objectAnimator
android:propertyName="string" //动画的属性名称。比如:"alpha"或 "backgroundColor"等
android:duration="int" //动画持续时间,默认值为300ms。
android:valueFrom="float | int | color" //动画开始的值。颜色表示为六位十六进制数字(例如#333333)
android:valueTo="float | int | color" //动画结束的值。颜色表示为六位十六进制数字(例如#333333)
android:startOffset="int" //动画延迟的毫秒数
android:repeatCount="int" //动画重复的次数。设置为"-1"无限重复或正整数。例如,值"1"表示动画在动画的初始运行后重复一次,因此动画总共播放两次。默认值是"0",这意味着没有重复。
android:repeatMode=["repeat" | "reverse"] //动画重复的行为模式。设置为"reverse" 使动画反转方向与每次迭代或每次"repeat"从一开始就有动画循环。
android:valueType=["intType" | "floatType"]/> //参数值的类型,与android:valueFrom,android:valueTo类型对应。如果值为颜色,则不要指定此属性。
<animator
android:duration="int"
android:valueFrom="float | int | color"
android:valueTo="float | int | color"
android:startOffset="int"
android:repeatCount="int"
android:repeatMode=["repeat" | "reverse"]
android:valueType=["intType" | "floatType"]/>
<set>
...
</set>
</set>
最后,在代码中调用改xml文件,通过AnimatorInflater类的loadAnimator(Context context, @AnimatorRes int id)方法加载,并设置目标对象即可:
Animator animator = AnimatorInflater.loadAnimator(this, R.animator.property_set);
animator.setTarget(mTarget);
animator.start();
如果还不是很清楚,可以看源码的具体实现。
在实际开发,如果不考虑复用性,建议使用代码来实现属性动画,比较简单,重要的是,很多时候一个属性的起始值是无法提前确定的,比如让button从屏幕的左边移动屏幕的右边,由于我们无法提前知道屏幕的宽度,因此无法在xml定义,这时我们就必须在代码中动态的创建属性动画了。
TypeEvaluator(估值器)
估值器的作用是:告诉属性动画,如何计算给定的属性值。即根据Animator类提供的属性变化的数值:开始值,结束值,然后根据设置的估值器,从开始值 过渡到 结束值。比如:ValueAnimator.ofInt()方法,系统内置了一个IntEvaluator,从而实现了初始值与结束值之间的平滑过度,并返回整形的结果。来看一下IntEvaluator的代码实现:
public class IntEvaluator implements TypeEvaluator<Integer> {
/**
* @param fraction 动画完成的比例:开始值与结束值的比例
* @param startValue 动画的开始值
* @param endValue 动画的结束值
* @return 对象动画过渡的逻辑计算后的值
*/
public Integer evaluate(float fraction, Integer startValue, Integer endValue) {
int startInt = startValue;
return (int)(startInt + fraction * (endValue - startInt));
}
}
可以看到,IntEvaluator 实现了TypeEvaluator接口,并重写了evaluate()方法,里面就是计算的逻辑。如果了解插值器(如需了解可以查看:
Android动画之Interpolator(插值器)),会觉得他们的套路是一样一样的。evaluate()里面有三个参数,在上面的代码已有注释解释了。里面算法的逻辑也很简单,简单讲,根据 fraction 计算出动画完成值,再加上开始值,返回当前动画值。
这个是系统对Int类型采用默认的估值器,如果你有其他需求,完全可以重新定义一个自己的IntEvaluator 。
那么,还有那些估值器呢?如下:
估值器 | 说明 |
---|---|
IntEvaluator | 默认计算值为int的属性 |
FloatEvaluator | 默认计算值为float的属性 |
ArgbEvaluator | 默认计算值为十六进制值颜色的属性 |
TypeEvaluator | 一个接口,可以实现该接口自定义估值器。如果动画对象的属性,不是一个int,float或color类型,那么必须实现TypeEvaluator指定如何计算对象属性的动画值接口。当然,如果你想以不同的方式处理这些类型比默认的行为,也可以重新自定义类型为int,float和color的估值器。 |
PointFEvaluator | 用于有坐标类型的对象,比如点 |
在ValueAnimator 类中,有ofObject(TypeEvaluator evaluator, Object... values)方法,如果参数类型不是int、float、color类型,那么就需要自己实现估值器了。平时也较少用到,这里就不再介绍了。
这里顺便说一个插值器与估值器的区别:
插值器:决定值的变化速率;
估值器:决定返回值的具体变化
ViewPropertyAnimator的使用
视图动画只能对View对象操作,是面对对象的操作。而属性动画的机制已经不是再针对于View而进行设计的了,而是一种不断地对值进行操作的机制,它可以将值赋值到指定对象的指定属性上,不再是面对对象的思维了。但是我们大部分使用还是对View的操作,因此在Android 3.1系统当中补充了ViewPropertyAnimator。
它的行为很像ObjectAnimator,因为它修改视图属性的实际值,针对同时许多动画属性时,更有效。比如:
ObjectAnimator animX = ObjectAnimator.ofFloat(mTarget, "x", 50f);
ObjectAnimator animY = ObjectAnimator.ofFloat(mTarget, "y", 100f);
AnimatorSet animSetXY = new AnimatorSet();
animSetXY.playTogether(animX, animY);
animSetXY.start();
使用ViewPropertyAnimator:
mTarget.animate().x(50f).y(100f);
可以看到,实在简洁太多了。。。ViewPropertyAnimator并不难:
private void animatePropertyBy(int constantName, float startValue, float byValue) {
........
mView.removeCallbacks(mAnimationStarter);
mView.postOnAnimation(mAnimationStarter);
}
private Runnable mAnimationStarter = new Runnable() {
@Override
public void run() {
startAnimation();
}
};
public void start() {
mView.removeCallbacks(mAnimationStarter);
startAnimation();
}
mTarget.animate().yBy(200f).rotation(360).scaleY(2).setDuration(3000).setInterpolator(new LinearInterpolator()).start();
如需源码点这里:点我
主要参考:
https://developer.android.google.cn/guide/topics/graphics/prop-animation.html
http://blog.youkuaiyun.com/guolin_blog/article/details/43536355