首页 > 代码库 > Property Anim详解
Property Anim详解
前言:
上一篇文章传统View动画与Property动画基础及比较简单 对Android动画系统的简单基础做了一些比较,本篇文章将对PropertyAnimation进行全面深入的探讨,有理解不准确的地方,欢迎大家指正。
官方说了Property Animation是一个很强劲的动画框架,几乎可以为所有的事物加上动画效果。你可以定义一个动画去改变任何对象的属性,不管该对象是否在屏幕上,都可以进行绘制。一个属性动画在某一个时间段,改变的是一个对象的一个属性值(一个对象的一个字段)。
属性动画系统为动画供了以下属性:
Duration:动画的持续时间
TimeInterpolation: 用于定义动画变化率的接口,所有插值器都必须实现此接口,如线性,非线性插值器。
TypeEvaluator: 用于定义属性值计算方式的接口,有int,float,color类型,根据属性的起始、结束值和插值一起计算出当前时间的属性值
Animation sets: 动画集合,即可以同时对一个对象应用多个动画,这些动画可以同时播放也可以对不同动画设置不同的延迟
Frame refreash delay: 多少时间刷新一次,即每隔多少时间计算一次属性值,默认为10ms,最终刷新时间还受系统进程调度与硬件的影响
Repeat Country and behavoir:重复次数与方式,如播放3次、5次、无限循环,可以让此动画一直重复,或播放完时向反向播放
一、Property Animation的工作方式
1.1 示例
示例1:线性动画
简单理解为匀速,下面描述了一个物体的X属性的运动。该对象的X坐标在40ms内从0移动到40 pixel,每10ms刷新一次,移动4次,每次移动40/4=10pixel。
简单的理解为非匀速,同样的40pixel,同样的时间,但是速率不同,开始和结束的速度要比中间部分慢,即先加速后减速
1.2、属性动画的几个重要组成部分
TimeInterpolator 实现插值器的接口,用于计算插值。
TypeAnimator 计算属性值的接口。
ValueAnimator 已经实现了TimeInterpolator和TypeAnimator接口,跟踪了动画时间的相关属性,比如一个动画已完成了多长时间,当前执行动画的开始、结束或属性值。
1.3、动画的计算过程
过程一:计算已完成动画分数 elapsed fraction
为了执行一个动画,你需要创建一个ValueAnimator,并且指定目标对象属性的开始、结束值和持续时间。在调用start后,整个动画过程中, ValueAnimator会根据已经完成的动画时间计算得到一个0到1之间的分数,代表该动画的已完成动画百分比。0表示0%,1表示100%,比如,示例1中,总时间 t = 40 ms,t = 10 ms 的时候是 0.25。过程二:计算插值(动画变化率)interpolated fraction
当ValueAnimator计算完已完成动画分数后,它会调用当前设置的TimeInterpolator,去计算得到一个interpolated(插值)分数,在计算过程中,已完成动画百分比会被加入到新的插值计算中。如示例2中,因为动画的运动是缓慢加速的,它的插值分数大约是 0.15,小于在 t = 10ms 时的已完成动画分数0.25。而在示例1中,这个插值分数一直和已完成动画分数是相同的。关于插值器的详细介绍,可以看2.3节。
过程三:计算属性值
当插值分数计算完成后,ValueAnimator 会根据插值分数调用合适的 TypeEvaluator去计算运动中的属性值。
以上分析引入了两个概念:已完成动画分数(elapsed fraction)、插值分数( interpolated fraction )。
在上面的示例2中,TimeInterpolator 使用的是 AccelerateDecelerateInterpolator ,而它的TypeEvaluator使用的是 IntEvaluator。
明白具体的过程后,我们来分析一下它的计算过程,取 t = 10ms:
过程1:计算已完成动画时间分数:t=10ms/40ms=0.25.
过程2:因为上述例子中用了AccelerateDecelerateInterpolator,其计算公式如下(input即为时间因子),经计算得到的插值大约为0.15:
public float getInterpolation(float input) { return (float)(Math.cos((input + 1) * Math.PI) / 2.0f) + 0.5f; }
这里简单说下,Interpolator接口的直接继承自TimeInterpolator,内部没有任何方法,而TimeInterpolator只有一个getInterpolation方法,所以所有的Interpolator只需实现getInterpolation方法即可。下面是AccelerateDecelerateInterpolator的源码:
public class AccelerateDecelerateInterpolator implements Interpolator { public AccelerateDecelerateInterpolator() { } @SuppressWarnings({"UnusedDeclaration"}) public AccelerateDecelerateInterpolator(Context context, AttributeSet attrs) { } public float getInterpolation(float input) { return (float)(Math.cos((input + 1) * Math.PI) / 2.0f) + 0.5f; } }
过程3:因为它的TypeEvaluator类型为FloatEvaluator,计算公式如下,因为startValue = http://www.mamicode.com/0,所以经计算得到属性值:0.15*(40-0)= 6 pixel:
public Float evaluate(float fraction, Number startValue, Number endValue) { float startFloat = startValue.floatValue(); return startFloat + fraction * (endValue.floatValue() - startFloat); }
参数分别为上一步的插值分数、起始值、结束值。
相信大家看到这里,整个动画的计算过程应该是非常清楚了。
第六部分的源码分析详细的介绍了这3个过程的内部实现。
因为View Animation 系统已经在android.view.animation中定义了很多的插值器,你可以直接应用到你的属性动画中。 Animator虽然提供了创建动画的基本框架,但你不应该直接使用这个类,因为它只提供了很少的功能,需要去扩展才能完全支持动画。下面介绍的是一些属性动画系统中的主要类。
1.ValueAnimator
属性动画中的主要的时序引擎,如动画时间,开始、结束属性值,相应时间属性值计算方法等。包含了所有计算动画值的核心函数。也包含了每一个动画时间上的细节,信息,一个动画是否重复,是否监听更新事件等,并且还可以设置自定义的计算类型。
整个Property Animation动画有两个步聚:
1.计算属性值2.为目标对象的属性设置属性值,即应用和刷新动画。
ValueAnimiator只完成了第一步工作,如果要完成第二步,你必须监听由ValueAnimator计算得到的属性值,并修改目标对象。需要实现ValueAnimator .onUpdateListener 接口,自己去处理对象的动画逻辑,比如:
ValueAnimator animation = ValueAnimator.ofFloat(0f, 1f); animation.setDuration(1000); animation.addUpdateListener(new AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { Log.i("update", ((Float) animation.getAnimatedValue()).toString()); } }); animation.setInterpolator(new CycleInterpolator(3)); animation.start();
2.ObjectAnimator
继承自ValueAnimator,允许你指定要进行动画的对象以及该对象的一个属性。该类会根据计算得到的新值自动更新属性。也就是说上Property Animation的两个步骤都实现了。大多数的情况,你使用ObjectAnimator就足够了,因为它使得目标对象动画值的处理过程变得简单,不用再向ValueAnimator那样自己写动画更新的逻辑。但ObjectAnimator有一定的限制,比如它需要目标对象的属性提供指定的处理方法,这个时候你需要根据自己的需求在ObjectAnimator和ValueAnimator中做个选择了,看哪种实现更简便。在下面的第三部分有重点介绍。
3.AnimationSet
动画集合,提供了一个把多个动画组合成一个组合的机制,并可设置组中动画的时序关系,如同时播放、顺序播放或延迟播放。Elevator会告诉属性动画系统如何计算一个属性的值,它们会从Animator类中获取时序数据,比如开始和结束值,并依据这些数据计算动画的属性值。
4.TimeAnimator
它并不能直接实现动画效果,它是一个对监听者的简单回调机制,在TimeListener接口的onTimeUpdate回调方法中返回动画持续的时间与上次调用的间隔时间,没有duration、interpolation以及设置值的方法等。主要是在动画的每一帧的时候Notify其监听者做相应的处理。
更详细的分析和在实际使用中如何选择,请参考第三部分。
2.2 Evaluators
Evaluators 告诉属性动画系统如何去计算一个属性值。它们通过Animator提供的动画的起始和结束值去计算一个动画的属性值。
属性系统提供了以下几种Evaluators:
1.IntEvaluator2.FloatEvaluator
3.ArgbEvaluator
这三个由系统提供,分别用于计算int,float,color型(十六进制)属性的计算器4.TypeEvaluator
一个用于用户自定义计算器的接口,如果你的对象属性值类型,不是int,float,或者color类型,你必须实现这个接口,去定义自己的数据类型。
更详细的介绍,请参考第五部分:使用TypeEvaluator
2.3 Interpolators
插值器:时间的函数,定义了动画的变化律。
插值器只需实现一个方法:getInterpolation(float input),其作用就是把0到1的elapsed fraction变化映射到另一个interpolated fraction。传入参数是正常执行动画的时间点,返回值是用户真正想要它执行的时间点。传入参数是{0,1},返回值一般也是{0,1}。{0,1}表示整段动画的过程。中间的0.2、0.3等小数表示在整个动画(原本是匀速的)中的位置,其实就是一个比值。如果返回值是负数,会沿着相反的方向执行。如果返回的是大于1,会超出正方向执行。也就是说,动画可能在你指定的值上下波动,大多数情况下是在指定值的范围内。
getInterpolation(float input)改变了默认动画的时间点elapsed fraction,根据时间点interpolated fraction得到的是与默认时间点不同的属性值,插值器的原理就是通过改变实际执行动画的时间点,提前或延迟默认动画的时间点来达到加速/减速的效果。动画插值器目前都只是对动画执行过程的时间进行修饰,并没有对轨迹进行修饰。
简单点解释这个方法,就是当要执行input的时间时,通过Interpolator计算返回另外一个时间点,让系统执行另外一个时间的动画效果。
经过动画计算过程的第一步,会获取一个已完成时间百分比elapsed fraction,也就是getInterpolation方法的参数input。插值器,就是时间的函数,插值就是函数值。Android动画提供的AccelerateDecelerateInterolator的源码为:
AccelerateDecelerateInterpolator public float getInterpolation(float input) { return (float)(Math.cos((input + 1) * Math.PI) / 2.0f) + 0.5f; }在下面的图中,也可以看到AccelerateDecelerate的Formula(公式)和其getInterpolation(input)方法相对应的函数值。
截图来自:http://cogitolearning.co.uk/?p=1078,该文章也有关于Android Property Anim的介绍,有兴趣的可以看一下。
下面我们再通过AccelerateDecelerate的函数图来进一步分析。
该曲线图,表现了动画计算的两个过程:X轴是时间因子(正好最大值为1,那么每个X轴上的值就可以看做是百分比),也就是动画计算过程的第一步所得到的值,Y轴就是相应时间的插值,就是动画计算过程的第二步。还有一步,这里没有体现出来,就是通过TypeEvaluator计算最终的属性值。
下面介绍几种插值器:
AccelerateInterpolator 加速,开始时慢中间加速
DecelerateInterpolator 减速,开始时快然后减速
AnticipateInterpolator 反向 ,先向相反方向改变一段再加速播放
AnticipateOvershootInterpolator 反向加超越,先向相反方向改变,再加速播放,会超出目的值然后缓慢移动至目的值
BounceInterpolator 跳跃,快到目的值时值会跳跃,如目的值100,后面的值可能依次为85,77,70,80,90,100
CycleIinterpolator 循环,动画循环一定次数,值的改变为一正弦函数:Math.sin(2 * mCycles * Math.PI * input)
LinearInterpolator 线性,线性均匀改变
OvershottInterpolator 超越,最后超出目的值然后缓慢改变到目的值
TimeInterpolator 一个接口,允许你自定义interpolator,以上几个都是实现了这个接口
如果这些插值器不能满足你的需求,那么你可以通过实现TimeInterpolator接口去创建自己的插值器。下面是 LinearInterpolator计算插值的方法,LinearInterpolator(线性插值器)对于已完成动画百分比没有影响。
LinearInterpolator
public float getInterpolation(float input) { return input; }
三、应用动画
ValueAnimator类可以为一些动画指定一系列的int,float,color值。通过调用工厂方法ofInt(),ofFloat().ofObject()来获取一个ValueAnimator.
ValueAnimator animation = ValueAnimator.ofFloat(0f, 1f); animation.setDuration(1000); animation.start();
上面这段是无效的代码,因为这里根本就没有动画目标的影子,也没有在ValueAnimator的监听中获取计算得到的属性值去更新目标对象,所以不会有动画效果。
你需要为动画指定一个自定义的类型:
ValueAnimator animation = ValueAnimator.ofObject(new MyTypeEvaluator(), startPropertyValue, endPropertyValue); animation.setDuration(1000); animation.start();
ValueAnimator通过MyTypeEvalutor提供的逻辑去计算一个时长为1000ms的动画在开始和结束之间的属性值,从start方法开始算起。第一块代码,对于对象没有起到真正的效果,你通常希望通过计算得到的属性值去修改动画对象,但这里的ValueAnimator没有直接操作一个对象或者属性。你需要在ValueAnimator中实现一个AnimatorUpdateListener监听去手动更新目标对象的属性值以及处理动画生命周期中的其它重要事件,如frame的更新。当你实现了监听之后,你可以通过getAnimateValue()方法获取某一帧的动画值,然后做更新操作。更多关于Listeners的介绍,你可以参考第四部分:Animation Listeners
更加简便,动画属性会自动更新,不用再像ValueAnimator那样自己去实现更新的动画逻辑,但需要遵循一定的规则。
实例化一个ObjectAnimator与实例化一个ValueAnimator是类似的,但是你应该指定对象和对象的某一属性的名字(String 类型),以及动画的起始和结束值。
ObjectAnimator anim = ObjectAnimator.ofFloat(foo, "alpha", 0f, 1f); anim.setDuration(1000); anim.start();
ObjectAnimator的自动更新功能,依赖于属性身上的setter和getter方法,所以为了让ObjectAnimator能够正确的更新属性值,你必须遵从以下规范:
a.添加setter方法
b.使用包装类。通过该包装类通过一个有效的setter方法获取或者改变属性值的方法,然后应用于原始对象。
c.使用ValueAnimator代替。
(这3点的意思总结起来就是一定要有一个setter方法,让ObjectAnimator能够访问到)
2. 如果你为ObjectAnimator的工厂方法的可变参数只传递了一个值,那么会被作为动画的结束值。因此,你的目标对象属性上必须要有一个getter方法,用于获取动画的起始值。这个获取方法必须使用get<propertyName>()的格式。例如,属性是foo,就必须有一个getFoo方法。
targetObject.setPropName(float) 和targetObject.getPropName(float) :
ObjectAnimator.ofFloat(targetObject, "propName", 1f)
4. 根据动画的目标属性或者对象不同,你可能需要调用某个View的invalidate方法,根据新的动画值去强制屏幕重绘该View。可以在onAnimateonUpdate()回调方法中去做。比如,对一个Drawable的颜色属性进行动画,只有当对象重绘自身的时候,才会导致该属性的更新,(不像平移或者缩放那样是实时的)。一个VIew的所有setter属性方法,比如setAlpha()和setTranslationX()都可以适当的更新View。因此你不需要在重绘的时候为这些方法传递新的值。更多关于 Listener的信息,可以参考第四部分Animation Listeners。
简单总结下:
当你不希望向外暴露Setter方法的时候,或者希望获取到动画值统一做处理的话,亦或只需要一个简单的时序机制的话,那么你可以选择使用ValueAnimator,它更简单。
如果你就是希望更新动画,更简便的,可以使用ObjectAnimator,但你必须有setter和getter方法,并且它们必须都是标准的驼峰式(确保内部能够调用),必须有结束值。
根据需要,不需实时更新的动画,需要你自己去强制更新。
3.3、AnimatorSet编排多个动画
很多时候,你需要在一个动画的开始或者结束点去播放另一个动画,Android系统允许你绑定多个动画到一个AnimatorSet中,因此你可以指定这些动画是否同时启动或者有序或者延迟进行。你也可以互相内嵌AnimatorSet。下面的代码来自Google Sample弹力球Sample,按顺序播放了以下动画:
2、同时播放 squashAnim1, squashAnim2, stretchAnim1, and stretchAnim2
3、播放 bounceBackAnim.
4、播放 fadeAnim.
AnimatorSet bouncer = new AnimatorSet(); bouncer.play(bounceAnim).before(squashAnim1); bouncer.play(squashAnim1).with(squashAnim2); bouncer.play(squashAnim1).with(stretchAnim1); bouncer.play(squashAnim1).with(stretchAnim2); bouncer.play(bounceBackAnim).after(stretchAnim2); ValueAnimator fadeAnim = ObjectAnimator.ofFloat(newBall, "alpha", 1f, 0f); fadeAnim.setDuration(250); AnimatorSet animatorSet = new AnimatorSet(); animatorSet.play(bouncer).before(fadeAnim); animatorSet.start();
更多细节,你可以参考APIDemo,APIDemo在大家的SDK中都有,直接导入即可。
四、Animation Listeners
你可以通过以下监听器监听动画过程中的重要事件:
onAnimationStart() - 动画启动时的回调
onAnimationEnd() -动画结束时的回调
onAnimationRepeat() - 动画重复自身时候回调
onAnimationCancel() - 动画被取消的时候回调,一个动画取消的时候也会调用onAnimationEnd方法,而不考虑动画是如何结束的。
onAnimationUpdate() :动画的每一帧都会调用该方法,监听该事件去使用ValueAnimator计算得到的值。通过getAnimatedValue方法可以获取当前的动画值。如果你使用 的是ValueAnimator,实现该监听就是有必要的了。
根据动画的属性的实际情况,你可能需要根据新的动画值去调用某个View身上的invalidate方法去强制刷新某一个区域。这一点和ObjectAnimator中的第4点相同。
AnimatorListenerAdapter类提供了一些空的实现,你可以选择性的覆盖。比如API中弹力球sample,创建了一个AnimatorListenerAdapter,而只实现了onAnimationEnd方法。
ValueAnimator fadeAnim = ObjectAnimator.ofFloat(newBall, "alpha", 1f, 0f); fadeAnim.setDuration(250); fadeAnim.addListener(new AnimatorListenerAdapter() { public void onAnimationEnd(Animator animation) { balls.remove(((ObjectAnimator)animation).getTarget()); }
如果你想添加一种动画系统中没有的计算类型,就需要自己通过实现TypeEvaluator接口去创建自己的evaluator。Android系统可以识别的类型是:int,float或者color。对应的java类分别为 IntEvaluator、 FloatEvaluator、 ArgbEvaluator 。
public class FloatEvaluator implements TypeEvaluator { public Object evaluate(float fraction, Object startValue, Object endValue) { float startFloat = ((Number) startValue).floatValue(); return startFloat + fraction * (((Number) endValue).floatValue() - startFloat); } }
我们再来看一下IntEvaluators的源码:
/** * This evaluator can be used to perform type interpolation between <code>int</code> values. */ public class IntEvaluator implements TypeEvaluator<Integer> { public Integer evaluate(float fraction, Integer startValue, Integer endValue) { int startInt = startValue; return (int)(startInt + fraction * (endValue - startInt)); } }ArgbEvaluator的部分源码,因为是十六进制颜色值,前部分做了一些位运算的操作,这里贴出的是最后返回值的代码:
return (int)((startA + (int)(fraction * (endA - startA))) << 24) | (int)((startR + (int)(fraction * (endR - startR))) << 16) | (int)((startG + (int)(fraction * (endG - startG))) << 8) | (int)((startB + (int)(fraction * (endB - startB))));
大家可以看到,三种计算器都是线性的,且形式都为: result = x0 + t * (v1 - v0)。
如果你的数据类型不是float,int,或者color类型,那么你就需要自己实现TypeEvaluator,并实现evaluate方法,根据自己的数据结构计算属性值。
代码家的开源动画库AnimationEasingFunctions就是根据一个函数库http://easings.net/zh-cn做出来的,每个不同的动画效果就是复写了evaluate方法,按照不同的函数计算属性值,从而达到了相应的动画效果。大家可以自己去看AnimationEasingFunctions的源码,在理解了1.3动画的计算过程后,再去看,就非常清晰了,关键地方就是这个evaluate方法根据不同的函数做了处理。
TimeInterpolator和TypeEvaluator的区别
不知道大家弄明白TypeEvaluator和TimeInterpolator没有,反正当时我刚看的时候,有些迷糊,不知道该如何具体的使用。
当时分析了代码家的AnimationEasingFunctions开源项目,发现它都是在TypeEvaluator中定义函数,而不是在TimeInterpolator中。
我当时很困惑,我的想法是在TimeInterpolator 中定义插值函数,而在Evaluators的evaluate方法只是简单的处理。比如系统提供的Evaluators那样,简单的进行线性运算即可,我当时对Evaluators的理解是:它只是为了扩展一种数据类型,比如系统提供的IntEvaluator、FloatEvaluator,它们内部计算只是简单的线性计算,只是类型不同而已。后来实在不太明白,就向代码家请教了下,代码家的答复:
Interpolator 和 evaluator 都是可以自定义函数的。 前者:只能修改fraction (多数场景可以满足,将原本线性的运动修改为非线性的) 后者:能拿到所有数据,然后去返回最终的值(终极利器,传入他的有三个参数 (float fraction, T startValue, T endValue)) getInterpolation(float input) evaluate(float fraction, Number startValue, Number endValue)
TypeEvalutor的evaluate方法接收的fraction究竟来自于哪里?
我觉得这个fraction非常重要,因为它连接了动画值计算的第二步和第三步,所以弄清楚它到底是什么,对于后续第三步属性值的计算非常重要。这里也在同一封邮件中向代码家请教过,代码家的答复是从第一个参数就是从 getInterpolator得到的。但是自己一直觉得哪里不对,后来经过Debug得出来了一些结果,也就是第六部分的来由,如果当初没有深入探索下去,就没有第六部分源码分析这一块,而最终收获良多,并且弄清了NineOldAndroids的实现原理。
首先说明下,在测试的时候,使用的是ObjectAnimator.ofFloat( )工厂方法,值类型为Float,所以内部逻辑使用了FloatKeyframeSet类(关于FloatKeyframeSet后面有详细的介绍,这里只需知道在该类里确定了传入Evaluator的fraction)的getFloatValu
FloatKeyframeSet.java
/* * 获取动画值,通过给定的elapsed fraction 和evaluators去计算中间值。该函数将传递的fraction映射到恰当的keyframe和fraction * 最终计算返回interpolated value. * 注意:传入的fraction可能落在[0-1]范围之外,这样的情况,我们只使用2个KeyFrameSet。只有2帧的时候,做了特别优化。 * 每一帧还可以拥有自己的Interpolator */ public float getFloatValue(float fraction) { if (mNumKeyframes == 2) {//对于只有两帧的情况,单独处理,做了优化 if (firstTime) { firstTime = false; firstValue = http://www.mamicode.com/((FloatKeyframe) mKeyframes.get(0)).getFloatValue();>
可以看到这里一共处理了4种情况:
mKeyframeNums == 2
fraction <= 0
fraction >= 1
fraction在(0,1)之间且mKeyframeNums != 2
我们先看看keyframe.getFraction()获取到的是什么值:
PropertyViewHolder
public void setFloatValues(float... values) { mValueType = float.class; mKeyframeSet = KeyframeSet.ofFloat(values); }
KeyframeSet.ofFloat(values)public static KeyframeSet ofFloat(float... values) { int numKeyframes = values.length; FloatKeyframe keyframes[] = new FloatKeyframe[Math.max(numKeyframes,2)]; if (numKeyframes == 1) { keyframes[0] = (FloatKeyframe) Keyframe.ofFloat(0f); keyframes[1] = (FloatKeyframe) Keyframe.ofFloat(1f, values[0]); } else { keyframes[0] = (FloatKeyframe) Keyframe.ofFloat(0f, values[0]); for (int i = 1; i < numKeyframes; ++i) { keyframes[i] = (FloatKeyframe) Keyframe.ofFloat((float) i / (numKeyframes - 1), values[i]);//这里是关键 } } return new FloatKeyframeSet(keyframes); }这里有个从角标1开始的for循环,循环调用Keyframe.ofFloat(fraction,value)工厂方法,创建Keyframe。第一个keyframe的fraction为0,这是默认的。
而其它关键帧fraction的计算方式我们可以看到:i / (numKeyframes-1), numKeyframes为用户传入到ObjectAnimator.ofFloat(Object target ,String PropertyName,float ...values) 方法的可变参数values个数。注意我们这里的value参数是动画运动的关键帧,和之前所说的动画运动的每一帧是不同的。运动过程中的每一帧是关键帧之间的那一部分,这部分是实时的,而关键帧就是一个个用户指定的属性值,希望在某个时间点(上述已经计算完成),达到的属性值。
mKeyframeNums = 2
返回的就是直接从参数中获取到的fraction,而这个fraction就是从通过ValueAnimator的Interpolator获取到的。所以在这种情况下,正如代码家的回复一样。
下面我们看一下源码中对getInterpolation()方法的注释:Value可以大于1或者小于0。
@return The interpolation value. This value can be more than 1.0 for * interpolators which overshoot their targets, or less than 0 for * interpolators that undershoot their targets. */ float getInterpolation(float input);在mKeyframeNums = 2 的时候,getInterpolation(input)的值会直接传入到evaluate中,而getInterpolation(input)的值可以是[0,1]之外的值。因此evaluate接收到的fraction就可能大于1,或者小于0,。大于1,说明波动比较大,获取到的属性值将大于目标值。
其实当初分析的时候,有一个误区,就是我所认为的evaluate中的fraction必需是[0,1]范围内的一个值,这样才适合作为一个比例值,所以对于getInterpolation(input)方法返回的值,在mKeyframeNums = 2 的时候,直接传递给Evaluator的evaluate方法,一直很困惑,最后才明白,getInterpolation(input)的值,其实不受约束的,完全可以由你自定义的插值函数来控制,最终计算得到的属性值,也不一定就比用户传入到ofFloat()中的Value小。事实确实是这样,动画的运动轨迹,是可以在你的指定的属性值上下波动的。
我们再看其它三种情况的处理:
float intervalFraction = (fraction - prevKeyframe.getFraction()) / (nextKeyframe.getFraction() - prevKeyframe.getFraction()); return mEvaluator == null ? prevValue + intervalFraction * (nextValue - prevValue) ://未定义Evaluators,则简单的返回,属性值计算结束 ((Number)mEvaluator.evaluate(intervalFraction, prevValue, nextValue)).//按照自定义的Evaluators来处理属性值 floatValue();fraction <= 0 和 fraction >= 1的情况相似,都是获取相邻两关键帧进行处理,但是它们选择的两关键帧是固定的,我个人认为这样的选择是为了更接近fraction。
假设用户传入的values 为 50,100,200,则numKeyframs = 3,那么创建出相应的Keyframe为:
Keyframe(0,50),Keyframe(1/2,100),Keyframe(1,200)
intervalFraction就是要传入Evaluator的evaluate方法的fraction。
fraction <= 0
final FloatKeyframe prevKeyframe = (FloatKeyframe) mKeyframes.get(0); final FloatKeyframe nextKeyframe = (FloatKeyframe) mKeyframes.get(1);选择的是第一帧(从上面的赋值来知道,第一帧的fraction为固定值0)和第二帧
prevkeyframeFraction = 0,nextKeyframeFraction = 1 / 2:
fraction >= 1
final FloatKeyframe prevKeyframe = (FloatKeyframe) mKeyframes.get(mNumKeyframes - 2); final FloatKeyframe nextKeyframe = (FloatKeyframe) mKeyframes.get(mNumKeyframes - 1);由mNumkeyframes-1,mNumkeyframes-2,可以知道,这里获取的就是倒数第一帧和倒数第二帧。
prevkeyframeFraction = 1/2 ,nextKeyframeFraction = 1:
mKeyframeNums != 2(或者==1,内部已处理为2)且在[0,1]范围内
上面逻辑中有这么一行代码: if (fraction < nextKeyframe.getFraction()) {...}
那么我们可以知道,这个elapsed fraction是某两关键帧区间的elapsed fraction,落到了某一关键帧和下一关键帧区间里。如图该fraction落在了1/2和1之间的区域:
上面更加清晰的知道,fraction并不一定在{0,1}内,也可能是该区间外的一个值,只是系统为了更接近这个fraction,在做处理的时候,选择两个相近的fraction进行计算,得到一个internalFraction传递给Evaluator的evaluate方法去计算属性值。
因此这里可以解决我上面疑问了,evaluate接受的fraction分为两种:
当用户传入的属性值是2个的时候:是getInterpolator()返回的fraction。
其它情况又分为3种,fraction>=1 和 fraction<=1的取值是固定的两关键帧,0<fraction<1时,为第一帧和大于fraction的那一帧。
兜了一大圈,其实就是为了弄清楚这个fraction到底是个什么值,现在明白了,其实只要知道这个fraction不一定是{0,1}之间的值,就OK了,就没有什么疑问了。
小结:
TypeEvaluator: 定义了属性值的计算方式,有int,float,color类型,根据属性的开始、结束值和插值一起计算出当前时间的属性值,终极方法,整个计算过程的结尾。TimeInterpolation: 插值器都必须实现的接口,定义了动画的变化率,如线性,非线性。
ValueAnimator与ObjectAnimator:两者都可以进行属性动画,但是ObjectAnimator更加简单,不用去做更新属性值的计算,但是必须要提供标准的setter和getter方法,让ObjectAnimator能够获取到属性值。
六、通过源码的角度来分析整个动画的全过程
先说明一下整个过程的分析是基于Jake Wharton的NineOldAndroids的,但除了初始化和动画的更新不同,其它的整体逻辑和思路是一样的,只是有些细节实现不同,毕竟大神不可能完全copy过来,有自己的代码习惯。所以大家不用担心和Android系统的源码有太大出入,而对于NineOldAndroids的原理分析部分,着重谈到了实现原理以及初始化和动画更新部分与系统动画的不同之处。
整个动画值的初始化过程:
初始化过程,就是由ObjectAnimator.ofFloat();方法开始所做的一系列工作
/* * 返回一个ObjectAnimator对象, * 如果可变参数values: * 只传递1个值:那么该值作为属性结束值, * 传2个值,则为起始和结束值。 * 2个以上,则起始值,过渡值,结束值 */ public static ObjectAnimator ofFloat(Object target, String propertyName, float... values) { ObjectAnimator anim = new ObjectAnimator(target, propertyName); anim.setFloatValues(values); return anim; }ValueAnimator.java /** * 主要是设置values值 * 如果不是通过工厂方法获取ObjectAnimator对象,而是通过构造函数的方式,并且values为null或者没有传值,那么 * 那么就从该属性身上去获取并设置值。属性也没有的话,那么根据用户传递的属性名去获取并设置值。 */ @Override public void setFloatValues(float... values) { if (mValues == null || mValues.length == 0) { // No values yet - this animator is being constructed piecemeal. Init the values with // whatever the current propertyName is if (mProperty != null) { setValues(PropertyValuesHolder.ofFloat(mProperty, values)); } else { setValues(PropertyValuesHolder.ofFloat(mPropertyName, values)); } } else { super.setFloatValues(values); } } /** * 接下来看一下有属性值的情况,会走父类ValueAnimator的setFloatValues, * 这个方法里面会执行到,如果你已经通过多个PropertyValuesHolder为多个对象定义了多对动画值。那么会为第一个对象赋值。 * (每一个对象的值集合对应一个PropertyValuesHolder) * 说明:对于ValueAnimator来说,通常应该传递2个或2个以上的属性值,因为它没法像ObjectAnimator那样去根据属性去 * 获取起始值。 * * @param values */ public void setFloatValues(float... values) { if (values == null || values.length == 0) { return; } if (mValues == null || mValues.length == 0) { setValues(PropertyValuesHolder.ofFloat("", values)); } else { PropertyValuesHolder valuesHolder = mValues[0]; valuesHolder.setFloatValues(values); } // New property/values/target should cause re-initialization prior to starting mInitialized = false; }/** * * PropertyValuesHolder持有Property属性信息和动画过程中的值,它可以通过ValueAnimator 或者 ObjectAnimator创建多个并行的动画。除了一些工 * 厂方法,还拥有设置和获取属性get、set方法的函数:getPropertyFunction,setupSetterOrGetter,还有更新属性值的方法setAnimatedValue */ PropertyValuesHolder setFloatValues public void setFloatValues(float... values) { mValueType = float.class; mKeyframeSet = KeyframeSet.ofFloat(values); }KeyframeSet.java
大家可以先略过KeyframeSet的介绍,可以直接看下面的代码逻辑,等整体走了一遍,回过头来再看也行。keyframeset只有两种函数,一种是工厂函数,int float 值作为KeyFrame工厂函数的值,或者直接接收keyFrame类型。 一种是返回value的函数,也就一个:getValue(float fraction),这里就是重头了,动画值计算过程的第三步在这里进行。
KeyFrame的Interpolator每个KeyFrame其实也有个Interpolator。如果没有设置,默认是线性的。我们之前设置的Interpolator是整个动画的,而系统允许你为每一KeyFrame的单独定义Interpolator,系统这样做的目的是允许你在某一个keyFrame做特殊的处理,也就是整体上是按照你的插值函数来计算,但是,如果你希望某个或某些FrameKey会有不同的动画表现,那么你可以为这个keyFrame设置Interpolator。如果这个KeyFrame的Interpolator设置了,那么由animationFrame(long currentTime)传递进来的fraction就需要被重新赋值为当前KeyFrame的Interpolator。
用户在初始化的时候只传递一个属性值的情况:
如果你在ObjectAnimator中传入的属性值,只有一个,那么默认的它会给你创建一个值为0的keyFrame,所以KeyframeSet的大小永远是大于等于2的。在FloatKeyframeSet中专门对2个属性值进行了处理,在getValue中计算的时候,就会进入 if (mNumKeyframes == 2)的逻辑,由于你只设置了一个keyFrame,它被作为lastKeyFrame处理,所以在构造函数中,我们可以看到:fraction = mLastKeyframe.getInterpolator()。如果传递了两个值,那么系统就会认为你是明确传递了2个值,就不会再为你添加起始值了。这两种情况都会进入 KeyframeSet getValue 方法的 if (mNumKeyframes == 2)的逻辑。
/** * KeyFrame的集合,被ValueAnimator调用去计算keyframe之间的动画值,因为它是封装了KeyFrame的存储、使用细节,所以也放在animation包下 * 类型明确的KeyframeSet子类,相对于那些自定义的TypeEvaluators来说在getValue方法上有速度的提升。因为它不需要对那些原始类型进行自动包装 */ public static KeyframeSet ofFloat(float... values) { boolean badValue = http://www.mamicode.com/false;>返回一个相应的FloatKeyframeSet
public FloatKeyframeSet(FloatKeyframe... keyframes) { super(keyframes); }
KeyframeSet.java
public KeyframeSet(Keyframe... keyframes) { mNumKeyframes = keyframes.length; mKeyframes = new ArrayList<Keyframe>(); mKeyframes.addAll(Arrays.asList(keyframes)); mFirstKeyframe = mKeyframes.get(0); mLastKeyframe = mKeyframes.get(mNumKeyframes - 1); mInterpolator = mLastKeyframe.getInterpolator(); }以上为赋值部分,最终返回了一个FloatKeyframeSet对象。
ValueAnimator持有一个PropertyValuesHolder的键值对:HashMap<String, PropertyValuesHolder>(numValues),
mValuesMap.put(valuesHolder.getPropertyName(), valuesHolder);代表一个或者多个属性值对象的集合
每个PropertyValuesHolder 持有一个KeyframeSet对象,上层调用下层去赋值,最底层是Keyframe
整个setFloatValues过程:
setFloatValues-------------> setValues(PropertyValuesHolder.ofFloat(mPropertyName, values));----------------->
PropertyValuesHolder : ofFloat(String propertyName, float... values) --------------------》setFloatValues(float... values)--------------->KeyframeSet ofFloat(float... values)
执行动画从start方法开始:
ValueAnimator:start(boolean playBackwards) ------------->
setCurrentPlayTime(getCurrentPlayTime())----------> animationFrame(long currentTime);--------------> animateValue(fraction);----------------->----------> 父类的animateValue(float fraction) --------> PropertyValuesHolder:calculateValue-------------> FloatKeyframeSet.getFloatValue(fraction);
ValueAnimator.java
/** * 参数为Boolean值,表示动画是否需要reverse,默认为false. * 调用该方法会在调用者所在的线程中启动动画,该线程应该拥有一个轮询器,如果对象的属性动画是View,那么应该 * 在UI线程中调用。 */ private void start(boolean playBackwards) { if (Looper.myLooper() == null) { throw new AndroidRuntimeException("Animators may only be run on Looper threads"); } mPlayingBackwards = playBackwards; mCurrentIteration = 0; mPlayingState = STOPPED; mStarted = true; mStartedDelay = false; sPendingAnimations.get().add(this); if (mStartDelay == 0) { // This sets the initial value of the animation, prior to actually starting it running setCurrentPlayTime(getCurrentPlayTime()); mPlayingState = STOPPED; mRunning = true; if (mListeners != null) { ArrayList<Animator.AnimatorListener> tmpListeners = (ArrayList<Animator.AnimatorListener>) mListeners.clone(); int numListeners = tmpListeners.size(); for (int i = 0; i < numListeners; ++i) { tmpListeners.get(i).onAnimationStart(this); } } } AnimationHandler animationHandler = sAnimationHandler.get(); if (animationHandler == null) { animationHandler = new AnimationHandler(); sAnimationHandler.set(animationHandler); } animationHandler.sendEmptyMessage(ANIMATION_START); }
以下全是setCurrentPlayTime(getCurrentPlayTime())方法内部的调用逻辑/** * 设置动画的指定起始时间点,如果动画没开始,只有设置了该时间点以后,动画才会开始执行。 * 如果动画已经在运行了,那么会为动画的时间重新赋值,并从该点继续执行, * 参数:动画起始点单位,单位:毫秒 */ public void setCurrentPlayTime(long playTime) { initAnimation(); long currentTime = AnimationUtils.currentAnimationTimeMillis(); if (mPlayingState != RUNNING) { mSeekTime = playTime; mPlayingState = SEEKED; } mStartTime = currentTime - playTime; animationFrame(currentTime); }animationFrame很重要的方法,这里是设置启动时间值调用到的,以后动画的每一帧都会调用该方法,rgb(255, 255, 255);">在这个方法里计算得到了elapsed fraction,传递到这里是动画计算过程的第一步(计算已完成动画时间比),也是每一帧动画都会执行的地方、后续帧的起始点
/* 处理某一帧的动画。该参数currentTime用于计算elapsed duration(已完成动画比)。 * 返回值用于判断动画是否已结束。(如果动画是repeat,那么根据repeatCount来加入计算) */ boolean animationFrame(long currentTime) { boolean done = false; if (mPlayingState == STOPPED) { mPlayingState = RUNNING; if (mSeekTime < 0) { mStartTime = currentTime; } else { mStartTime = currentTime - mSeekTime; // Now that we're playing, reset the seek time mSeekTime = -1; } } switch (mPlayingState) { case RUNNING: case SEEKED: float fraction = mDuration > 0 ? (float)(currentTime - mStartTime) / mDuration : 1f;<span class="comment" style="color: rgb(0, 130, 0); padding: 0px; margin: 0px; width: auto; border: 0px; font-family: Monaco, 'DejaVu Sans Mono', 'Bitstream Vera Sans Mono', Consolas, 'Courier New', monospace; line-height: 18px; background-color: rgb(250, 250, 250);">//</span><span class="comment" style="padding: 0px; margin: 0px; width: auto; border: 0px; font-family: Monaco, 'DejaVu Sans Mono', 'Bitstream Vera Sans Mono', Consolas, 'Courier New', monospace; line-height: 18px; background-color: rgb(250, 250, 250);">通过currentTime和mStartTime的差值计算动画执行的进度,0-1的小数值</span><span style="font-family: Monaco, 'DejaVu Sans Mono', 'Bitstream Vera Sans Mono', Consolas, 'Courier New', monospace; line-height: 18px; background-color: rgb(250, 250, 250);"> </span> if (fraction >= 1f) { if (mCurrentIteration < mRepeatCount || mRepeatCount == INFINITE) { // Time to repeat if (mListeners != null) { int numListeners = mListeners.size(); for (int i = 0; i < numListeners; ++i) { mListeners.get(i).onAnimationRepeat(this); } } if (mRepeatMode == REVERSE) { mPlayingBackwards = mPlayingBackwards ? false : true; } mCurrentIteration += (int)fraction; fraction = fraction % 1f; mStartTime += mDuration; } else { done = true; fraction = Math.min(fraction, 1.0f); } } if (mPlayingBackwards) { fraction = 1f - fraction; } animateValue(fraction); break; } return done; }
ObjectAnimator复写了父类的animateValue,所以这里先走ObjectAnimator的animateValue方法:
animateValue插值计算就在这里进行 /* 动画的每一帧都会调用的方法,该方法的目的是将elapsed fraction转变为一个 interpolated fraction,用于 * 动画的属性值(由evaluators计算得到)。该函数通常在动画更新的时候被调用,但end()方法执行的时候,也会被 * 调用,用于设置属性的最终值。 * 复写此方法的时候必须调用父类去计算动画属的性值。 * 参数为elapsed fraction */ @Override void animateValue(float fraction) { super.animateValue(fraction); int numValues = mValues.length; for (int i = 0; i < numValues; ++i) { mValues[i].setAnimatedValue(mTarget); } }
按照执行顺序,先看一下父类ValueAnimator的animateValue(fraction):动画计算过程的第二步和第三步在这里进行void animateValue(float fraction) { fraction = mInterpolator.getInterpolation(fraction);//计算的第二步:根据elapsed fraction使用Interpolation重新计算一个fraction mCurrentFraction = fraction; int numValues = mValues.length; for (int i = 0; i < numValues; ++i) { mValues[i].calculateValue(fraction);//计算的第三步:根据fraction,为每一个属性计算属性值,调用的是PropertyValuesHolder的calculate方法 } if (mUpdateListeners != null) { int numListeners = mUpdateListeners.size(); for (int i = 0; i < numListeners; ++i) { mUpdateListeners.get(i).onAnimationUpdate(this);//实现监听,更新动画的属性值 } } }计算的第三步是通过PropertyValuesHolder的getValue方法计算属性值:
void calculateValue(float fraction) { mAnimatedValue = http://www.mamicode.com/mKeyframeSet.getValue(fraction);>mKeyframsSet.getValue(fraction)方法的实现已经在第五部分:使用TypeEvaluator中为解决evaluate中的fraction的真正来源贴出。
注:因为测试使用的是ofFloat,所以在初始化的时候,PropertyViewHolder已经选择了FloatKeyFrameSet作为frame信息的处理,因此这里的mKeyframeSet.getValue()就会执行到FloatKeyFrameSet的getValue。
由于最终都是通过KeyFrame来处理这些值的,我们来看一看KeyFrame的定义:
/* 持有动画time/value的键值对。KeyFrame 累用于定义目标动画对象过程中的属性值。由于时间是在一帧到另一帧之间尽心个,所有目标对象的 * 动画值将会在前一帧值和后一帧值之间。每一帧持有了可选的TimeInterpolator对象,为每一帧单独设置Interpolator. * Keyframe本身是抽象类,指定类型的工厂函数将会根据保存的值类型返回一明确的Keyfame对象。系统对float和int类型的值,有性能的优化。 * 除非你需要处理一个自定义的类型或者要求直接应用动画的数据结构(或者实现TypeEvaluator)之外,你应该使用int或者float类型的值。
-------------------------------------------------------------------super.animateValue 结束 ,华丽的分割线-----------------------------------------------------------
接着执行,下面的逻辑很关键啊,做了更新属性值的操作,调用的是PropertyValuesHolder的setAnimateValue方法,使用了反射机制去更新属性值
PropertyValuesHolder[] mValues; for (int i = 0; i < numValues; ++i) { mValues[i].setAnimatedValue(mTarget); }PropertyValuesHolder的setAnimatedValue:重要方法,更新属性值void setAnimatedValue(Object target) { if (mProperty != null) { mProperty.set(target, getAnimatedValue()); } if (mSetter != null) { try { mTmpValueArray[0] = getAnimatedValue(); mSetter.invoke(target, mTmpValueArray);//这里就是设置属性值的方法了 } catch (InvocationTargetException e) { Log.e("PropertyValuesHolder", e.toString()); } catch (IllegalAccessException e) { Log.e("PropertyValuesHolder", e.toString()); } } }-------------------------------------------ObjectAnimator.animateValue 结束 ,setCurrentPlayTime(getCurrentPlayTime());结束-------------------------------------
到此AnimateValue方法执行完毕,那么setCurrentPlayTime(getCurrentPlayTime());也就执行完毕了。
那么我们回到 ValueAnimator的start(boolean playBackwards)方法,接着来的事就交给handler处理了。那么我们就看看Handler是如何驱动动画的。
private static class AnimationHandler extends Handler { /** * 有两种消息我们需要关心:ANIMATION_START 和 ANIMATION_FRAME * START消息:当start()方法被调用的时候,会发送一个START消息。当start()方法被调用的时候,动画的启动并不是同步的。 * 因为它可能是在错误的线程中被调用,并且由于每个动画的时序是不同的,所以也不可能与其它的动画同步。 * 每一个动画向Handler发送START消息的时候,就会触发Handler把该动画放到处于活动的动画队列中去,并开始 * 该动画的frame。 * FRAME消息:只要有活动状态的动画需要去处理,就会一直发送。 */ @Override public void handleMessage(Message msg) { boolean callAgain = true; ArrayList<ValueAnimator> animations = sAnimations.get(); //准备开始执行的动画 ArrayList<ValueAnimator> delayedAnims = sDelayedAnims.get();//需要延迟执行的动画 switch (msg.what) { // TODO: should we avoid sending frame message when starting if we // were already running? case ANIMATION_START: ArrayList<ValueAnimator> pendingAnimations = sPendingAnimations.get(); if (animations.size() > 0 || delayedAnims.size() > 0) { callAgain = false; } // pendingAnims holds any animations that have requested to be started // We're going to clear sPendingAnimations, but starting animation may // cause more to be added to the pending list (for example, if one animation // starting triggers another starting). So we loop until sPendingAnimations // is empty. while (pendingAnimations.size() > 0) { ArrayList<ValueAnimator> pendingCopy = (ArrayList<ValueAnimator>) pendingAnimations.clone(); pendingAnimations.clear(); int count = pendingCopy.size(); for (int i = 0; i < count; ++i) { ValueAnimator anim = pendingCopy.get(i); // If the animation has a startDelay, place it on the delayed list if (anim.mStartDelay == 0) { anim.startAnimation(); } else { delayedAnims.add(anim); } } } // 注意,这里没有break,ANIMATION_FRAME中代码将继续执行 case ANIMATION_FRAME: // 在当前帧内,currentTime对于所有的动画处理,持有相同的时间 long currentTime = AnimationUtils.currentAnimationTimeMillis(); ArrayList<ValueAnimator> readyAnims = sReadyAnims.get(); ArrayList<ValueAnimator> endingAnims = sEndingAnims.get(); int numDelayedAnims = delayedAnims.size();//如果延迟队列中的动画到了该启动的时候,那么它们加入到活动动画队列中去 for (int i = 0; i < numDelayedAnims; ++i) { ValueAnimator anim = delayedAnims.get(i); if (anim.delayedAnimationFrame(currentTime)) { readyAnims.add(anim); } } int numReadyAnims = readyAnims.size(); if (numReadyAnims > 0) { for (int i = 0; i < numReadyAnims; ++i) { ValueAnimator anim = readyAnims.get(i); anim.startAnimation();//该方法将将要执行的动画加入到活动动画队列中,这里其实并没有刷新属性值的方法 anim.mRunning = true; delayedAnims.remove(anim);//动画开始,将其从延迟动画中干掉 } readyAnims.clear(); } // 处理所有活动中的动画,animationFrame()的返回值决定该当动画是否执行完毕 int numAnims = animations.size(); int i = 0; while (i < numAnims) { ValueAnimator anim = animations.get(i); if (anim.animationFrame(currentTime)) {//关键代码,循环的去调用animationFrame方法,去进行一些列的计算,并更新属性值 endingAnims.add(anim);//如果该动画执行完毕,则加入到结束队列中去 } if (animations.size() == numAnims) {//动画没被取消,++i ++i; } else {//当前正在执行的动画被cancle的情况 // An animation might be canceled or ended by client code // during the animation frame. Check to see if this happened by // seeing whether the current index is the same as it was before // calling animationFrame(). Another approach would be to copy // animations to a temporary list and process that list instead, // but that entails garbage and processing overhead that would // be nice to avoid. --numAnims; endingAnims.remove(anim); } } if (endingAnims.size() > 0) { for (i = 0; i < endingAnims.size(); ++i) { endingAnims.get(i).endAnimation(); } endingAnims.clear(); } if (callAgain && (!animations.isEmpty() || !delayedAnims.isEmpty())) {//如果有活动或者延迟状态的动画,那么就继续发送FRAME消息,循环 sendEmptyMessageDelayed(ANIMATION_FRAME, Math.max(0, sFrameDelay - (AnimationUtils.currentAnimationTimeMillis() - currentTime))); } break; } } }在ANIMATION_FRAME消息处理的结尾结处:会判断是否还有活动或者延迟没执行的动画,如果有,则post一个ANIMATION_FRAME消息,然后就会再次执行动ANIMATION_FRAME消息的逻辑。如此反复,所有的动画将会执行完。那么大家肯定很好奇,既然一直在执行动画,那属性值的更新一定能从这里跟踪到,那在哪里呢?不用想,肯定是在startAnimation()方法中,点进去看看:
private void startAnimation() { initAnimation(); sAnimations.get().add(this); if (mStartDelay > 0 && mListeners != null) {<span style="font-family: Arial, Helvetica, sans-serif;">// startDelay == 0 的Listeners 已经在start()中notify了,这里只对延迟动画做处理</span> ArrayList<AnimatorListener> tmpListeners = (ArrayList<AnimatorListener>) mListeners.clone(); int numListeners = tmpListeners.size(); for (int i = 0; i < numListeners; ++i) { tmpListeners.get(i).onAnimationStart(this); } } }
这个是ValueAnimator的startAnimation方法,只是做了初始化动画和对延迟动画监听器回调的处理,(注意,这里initAnimation也被复写了,对于NineOldAndroids来说是很重要的方法,因为自定义属性的初始化就在这里进行的,为目标对象重新设置了兼容版本的属性),并没有更新值的方法啊,后来在AnimationHandler中,一个一个方法的点进去,跟踪到了animateFrame方法,该方法结尾处调用了animateValue方法,开始一直认为这个方法只是计算一些属性值,而且当时打断点的方式是直接根据Ctrl+B的方式进到方法里面打的断点,没有注意到实现类复写了方法,所以观察断点的时候,一走就走到了父类的方法中去。后来也是灵光一闪,感觉是ObjectAnimator是复写了父类的方法,做了一些自己特有的处理,后来一看,果然是,ObjectAnimator在这个复写的方法中进行了更新属性值的操作,真是豁然开朗啊,就此得出结论,整个分析过程就此完成了。在上面分析动画执行过程中,已经在代码块上面写上了结论,其实也是后来添加上去的。animationFrame方法是在ANIMATION_FRAME消息中,计算一个动画是否已经结束的时候调用的: while (i < numAnims) { ValueAnimator anim = animations.get(i); if (anim.animationFrame(currentTime)) {//大家可以看这里,循环的去animationFrame方法,去进行一些列的计算,如果该动画执行完毕,则加入到结束队列中去 endingAnims.add(anim); } }animateFrame方法,该方法在结尾处调用animateValue方法,这个是子类ObjectAnimator的animateValue方法:@Override void animateValue(float fraction) { super.animateValue(fraction);//做一些属性值的计算 int numValues = mValues.length; for (int i = 0; i < numValues; ++i) { mValues[i].setAnimatedValue(mTarget);//这里将属性值更新到了Target上去 } }这里使用了反射,将属性值设置到了目标对象上:
void setAnimatedValue(Object target) { if (mProperty != null) { mProperty.set(target, getAnimatedValue()); } if (mSetter != null) { try { mTmpValueArray[0] = getAnimatedValue(); mSetter.invoke(target, mTmpValueArray); } catch (InvocationTargetException e) { Log.e("PropertyValuesHolder", e.toString()); } catch (IllegalAccessException e) { Log.e("PropertyValuesHolder", e.toString()); } } }anim.startAnimation();在AnimationHandler的ANIMATION_FRAME消息处理逻辑中,执行一个动画,因为这里每次都会去执行监听方法。所以如果你使用的是ValueAnimator,那么在ValueAnimator的监听中,你必须手动做一些动画的操作去更新动画。
我们简单总结一下上述的整个分析过程:
start方法进入了setCurrentPlayTime(long)方法,该方法其实是设置了动画的第一帧操作,但它把整个动画的计算过程走了一遍。
其中animationFrame方法为计算过程的第一步,animateValue方法为第二步和第三步发生的地方,第二步就是调用getInterpolator()方法获取一个函数值,第三步则调用PropertyValuesHolder持有的KeyFrameSet.getValue()方法完成。而动画属性值的更新则发生在ObjectAnimator的animateValue(fraction) 方法中,也是通过PropertyValuesHolder,setAnimatedValue方法,内部使用了反射机制来调用属性的setter方法。后续的动画都在ValueAnimator的Handler中进行,只要还有活动和延迟的动画,就会一直循环执行。
到此整个动画的源码分析过程就结束了。大家可以松口气了,不过我感觉应该还好吧,上述分析还算连贯,应该能够顺溜的走下来。
NineOldAndroid兼容库的实现原理
以上的所有分析都是基于NineOldAndroid的,但是和系统的没什么太大区别,AnimationHandler的实现不同,Android系统的AnimationHandler是一个实现Runnable的子类,而NineOldAndroid的AnimationHandler就是一个继承Handler的子类,代码习惯不一样吧,但是我们要知道核心部分是去更新动画这部分,其它的部分代码不同,但是逻辑思路都应该是一样的。关键的地方是动画的初始化和动画的刷新。那么我们接下来分析这方面的差异。
首先我们必须得弄清楚的是,之前的View动画和现在的Property动画之间的差别:
View Anim,是不支持View自身属性值的变化的。它的响应事件仍然保留在原始地点,它完全是由其父控件来draw出来的。因此nineOldAndroid的做法就是进行计算出View新的位置。然后自己去处理移动刷新View的逻辑。
还记得之前我们说过的,ValueAnimator的缺点是需要通过实现一个属性值的方法,自己手动去更新属性值,这一缺点被它的子类ObjectAnimator所弥补,ObjectAnimator类为用户实现了自动更新。这个时候ObjectAnimator应该有自动更新属性值的功能,但问题在哪里呢?就是它只支持3.0以后的系统,那么这一优势也不再存在了,但是我们希望兼容库不打破原有的这种继承关系以及功能范围(意思就是兼容库版本的ObjectAnimator应该提供和系统的ObjectAnimator一样的所有属性支持),OK,兼容性的ObjectAnimator由此而来了。
nineOldAndroid兼容库里的做法是正式基于上述理论:在ObjectAnimator中自己定义和3.0 Property Anim系统所支持的相同属性,并提供setter和getter以及更新属性值的方法,这样就可以不依赖系统,而是由我们自己去更新View的属性。
我们来看一下nineOldAnroid兼容库:nineOldAndroid的结构,有哪些类:
除了PreHoneycombCompat和AnimatorProx外,我们经过上面的分析,已经都很熟悉了。
ViewHelper则是帮助类,你使用动画的时候,用这个类就可以了。
另外对于ViewPropertyAnimator提供了不同系统版本的兼容类:
ViewPropertyAnimatorICS、ViewPropertyAnimatorHC、ViewPropertyAnimatorPreHC
public static ViewPropertyAnimator animate(View view) { ViewPropertyAnimator animator = ANIMATORS.get(view); if (animator == null) { final int version = Integer.valueOf(Build.VERSION.SDK); if (version >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) { animator = new ViewPropertyAnimatorICS(view); } else if (version >= Build.VERSION_CODES.HONEYCOMB) { animator = new ViewPropertyAnimatorHC(view); } else { animator = new ViewPropertyAnimatorPreHC(view); } ANIMATORS.put(view, animator); } return animator; }
ViewPropertyAnimator是一个帮助类,使得同时创建多个属性动画变得更加方便,后面会介绍到。那么我们看看是如何初始化自定义属性的:
ObjectAnimator.java
@Override void initAnimation() { if (!mInitialized) { // mValueType may change due to setter/getter setup; do this before calling super.init(), // which uses mValueType to set up the default type evaluator. if ((mProperty == null) && AnimatorProxy.NEEDS_PROXY && (mTarget instanceof View) && PROXY_PROPERTIES.containsKey(mPropertyName)) { setProperty(PROXY_PROPERTIES.get(mPropertyName)); } int numValues = mValues.length; for (int i = 0; i < numValues; ++i) { mValues[i].setupSetterAndGetter(mTarget); } super.initAnimation(); } }
PreHoneycombCompat类:拥有所有的属性,向外暴露属性及属性方法,PreHoneycombCompat部分代码:
<pre name="code" class="java">final class PreHoneycombCompat { static Property<View, Float> ALPHA = new FloatProperty<View>("alpha") { @Override public void setValue(View object, float value) { AnimatorProxy.wrap(object).setAlpha(value); } @Override public Float get(View object) { return AnimatorProxy.wrap(object).getAlpha(); } }; static Property<View, Float> PIVOT_X = new FloatProperty<View>("pivotX") { @Override public void setValue(View object, float value) { AnimatorProxy.wrap(object).setPivotX(value); } @Override public Float get(View object) { return AnimatorProxy.wrap(object).getPivotX(); } }; } ..........而内部实现使用了代理类:AnimatorProxy。 关键是这个AnimatorProxy类,它是最终实现类。所有的属性的update都由它来处理,所有的处理都是先计算值然后通过matrix来实现。private void computeRect(final RectF r, View view) { .............. } private void transformMatrix(Matrix m, View view) { ............... } @Override protected void applyTransformation(float interpolatedTime, Transformation t) { ............. }另外, 在AnimatorProxy类中:每一个应用属性的方法,都会有以下两个步骤: prepareForUpdate();invalidateAfterUpdate(); 而动画的平移是通过Matrix来进行的。
看一下系统的实现,既然之前的系统不支持属性动画,那么通过代理来处理initAnimation()方法里设置属性的时候,都是nineOldAndroid版本的ObjectAnimator提供的: setProperty(PROXY_PROPERTIES.get(mPropertyName));
七、通过ViewPropertyAnimator添加动画
ViewPropertyAnimator:只使用一个Animator对象就可以为某个View的多个属性并行的添加动画。它的行为更像一个ObjectAnimator,因为它修改的是对象的实际属性值。但它为一次性给多个属性添加动画提供了方便,而且使用ViewPropertyAnimator的代码更连贯更易读。下面的代码段分别展示了使用多个ObjectAnimator对象、一个ObjectAnimator对象、 ViewPropertyAnimator同时为一个View的X和Y属性添加动画的示例:
多个ObjectAnimator属性
ObjectAnimator animX = ObjectAnimator.ofFloat(myView, "x", 50f); ObjectAnimator animY = ObjectAnimator.ofFloat(myView, "y", 100f); AnimatorSet animSetXY = new AnimatorSet(); animSetXY.playTogether(animX, animY); animSetXY.start();一个ObjectAnimator属性PropertyValuesHolder pvhX = PropertyValuesHolder.ofFloat("x", 50f); PropertyValuesHolder pvhY = PropertyValuesHolder.ofFloat("y", 100f); ObjectAnimator.ofPropertyValuesHolder(myView, pvhX, pvyY).start();
ViewPropertyAnimator:
myView.animate().x(50f).y(100f);//只需一行代码关于ViewPropertyAnimator更详细的介绍,可以参考:http://developer.android.com/reference/android/view/ViewPropertyAnimator.html
和相应的官方博客:http://android-developers.blogspot.jp/2011/05/introducing-viewpropertyanimator.html
八、为ViewGroup添加布局动画
你可以在ViewGroup内,通过LayoutTransition类为布局的变化添加动画。
当一个ViewGroup中添加或者移除某一个item,或者调用了View的setVisibility方法,使得View 变得VISIBLE或者GONE的时候,在ViewGroup内部的View可以完成出现或者消失的动画。当你添加或者移除View的时候,那些剩余的View也可以通过动画的方式移动到自己的新位置。你可以通过setAnimator()方法并传递一个Animator对象,在LayoutTransition内部定义以下动画。以下是几种事件类型的常量:APPEARING 为那些添加到父元素中的元素应用动画
CHANGE_APPEARING 为那些由于父元素添加了新的item而受影响的item应用动画
DISAPPEARING 为那些从父布局中消失的item应用动画
CHANGE_DISAPPEARING 为那些由于某个item从父元素中消失而受影响的item应用动画
你可以为这四种事件定义自己的交互动画,或者仅仅告诉动画系统使用默认的动画。
API Demos中的LayoutAnimations sample向你展示了如何为布局转换定义一个布局动画,然后将该动画设置到目标View对象上。
LayoutAnimationsByDefault和相应的布局文件 layout_animations_by_default.xml展示了如何为ViewGroup启动默认的转换动画,你唯一要做的事就是设置android:animateLayoutchanges为true
<LinearLayout android:orientation="vertical" android:layout_width="wrap_content" android:layout_height="match_parent" android:id="@+id/verticalContainer" android:animateLayoutChanges="true" />设置该属性为true后,那些从ViewGroup添加或者移除的View和剩余的View将会拥有动画的效果。
-------------------这些示例我们可以在Google提供的官方Sample中找到,以下是代码分析-------------------
九、指定Keyframes
一个keyframs对象由一个time/value的键值对组成,可以为动画定义某一特定时间的特定状态。每个keyframe可以拥有自己的插值器,用于控制前一帧和当前帧的时间间隔间内的动画。
Keyframe.ofFloat(0f,0f);
第一个参数为:要执行该帧动画的时间节点(elapsed time / duration),第二个参数为属性值。因此如果你想指定某一特定时间的特定状态,那么简单的使用ObjectAnimator就满足不了你了,因为,ObjectAnimator.ofInt(....)类似的工厂方法,无法指定特定的时间点。
为了实例化一个keyframe对象,你必须使用某一个工厂方法:ofInt(), ofFloat(), or ofObject() 去获取合适的keyframe类型,然后你调用ofKeyframe工厂方法去获取一个PropertyValuesHolder对象,一旦你拥有了该对象,你可以将PropertyValuesHolder作为参数获取一个animator,如下:Keyframe kf0 = Keyframe.ofFloat(0f, 0f); Keyframe kf1 = Keyframe.ofFloat(.5f, 360f); Keyframe kf2 = Keyframe.ofFloat(1f, 0f); PropertyValuesHolder pvhRotation = PropertyValuesHolder.ofKeyframe("rotation", kf0, kf1, kf2);//动画属性名,可变参数 ObjectAnimator rotationAnim = ObjectAnimator.ofPropertyValuesHolder(target, pvhRotation) rotationAnim.setDuration(5000ms);
更复杂的如何使用keyframes的例子,可以参考APIDemos中的 MultiPropertyAnimation。
十、拥有动画能力的View
View Animation系统是只允许为View添加动画的,其它的对象是不允许的。这里就介绍property animation 系统为View提供动画的情况,相对于View Animation,Property Animation更具优势。
View Animation 系统通过View被draw的方式去转化View。这是在每一个View的容器中处理的,由于View自身没有属性去操作。导致了View虽然实现了动画效果,但自身并未改变,这样导致的问题是,即使一个View通过动画被绘制到了不同的位置,但是它的行为仍然保留在原始的位置。
从Android 3.0开始,添加了新的属性和相应的setter和getter方法,解决了这种问题。属性动画改变的是一个对象的实际属性值。另外,View也会在属性值改变的时候会自动调用invalidate方法去更新屏幕。以下是几种新的属性:
translationX,translationY: View相对于原始位置的偏移量
rotation,rotationX,rotationY: 旋转,rotation用于2D旋转角度,3D中用到后两个
scaleX,scaleY: 2D缩放支点pivotX and pivotY: 缩放支点的位置,旋转和缩放在该支点周围进行。默认的缩放支点是对象的中心。x and y: 描述View在其父控件中的最终的位置,是由 left + translationX top + translationY 得来。
alpha: View的透明度,1表示完全不透明,0表示完全透明。
为一个对象的属性添加动画,你所需要做的就是创建一个属性animator并且指定目标属性:
ObjectAnimator.ofFloat(myView, "rotation", 0f, 360f);
大家都知道,对于alpha属性,在View Anim 系统中就有,个人的理解是,由于从3.0开始,动画机制完全不一样,一些属性的setter和getter方法暴露了,alpha也是其中一个,所以只要是具有Property Anim能力的属性都算是新添加的吧。
在XML中声明动画
property animation system 允许你使用XML声明属性动画而不是通过代码。通过在XML中定义的动画,可以很方便的在多个Activities中重用而且更容易编辑。为了区分新的属性动画,从3.1开始,你应该在res/animator/ 下存放属性动画的资源文件,使用animator文件夹是可选的,但是如果你想在Eclipse ADT插件中使用布局编辑工具(ADT 11.0.0+),就必须在res/animator文件夹下存放了,因为ADT只会查找res/animator文件夹下的属性动画资源文件。
属性动画支持的Tag有:
ValueAnimator
-<animator>
ObjectAnimator
-<objectAnimator>
AnimatorSet
-<set>
下面的示例有序的播放了两组动画,外层的那组动画内嵌了一组动画。
<set android:ordering="sequentially"> <set> <objectAnimator android:propertyName="x" android:duration="500" android:valueTo="400" android:valueType="intType"/> <objectAnimator android:propertyName="y" android:duration="500" android:valueTo="300" android:valueType="intType"/> </set> <objectAnimator android:propertyName="alpha" android:duration="500" android:valueTo="1f"/> </set>
当然,为了执行该动画,你还需要在Java代码中应用它们:
AnimatorSet set = (AnimatorSet) AnimatorInflater.loadAnimator(myContext, R.anim.property_animator); set.setTarget(myObject); set.start();
欢迎大家对本篇文章提出宝贵建议,如果有理解不到位的地方,还望指正。
GitHub:lightSky
微博: light_sky , 爱Android,爱开源,爱运动,欢迎交流
参考文献
http://developer.android.com/guide/topics/resources/animation-resource.html#Property
http://developer.android.com/guide/topics/graphics/prop-animation.html
http://android-developers.blogspot.jp/2011/02/animation-in-honeycomb.html
http://www.cnblogs.com/angeldevil/archive/2011/12/02/2271096.html
http://cogitolearning.co.uk/?p=1078
http://www.2cto.com/kf/201306/222725.html
http://my.oschina.net/banxi/blog/135633
http://zhouyunan2010.iteye.com/blog/1972789
Property Anim详解