前言:
上1篇文章传统View动画与Property动画基础及比较简单 对Android动画系统的简单基础做了1些比较,本篇文章将对PropertyAnimation进行全面深入的探讨,本篇文章可以分为两大块,从第6部份可以作为分界点。前5部份侧重讲授了PropertyAnim的动画值的计算进程,ValueAnimator与ObjectAnimator和TimeInterpolation与TypeEvaluator之间的介绍和比较,这几点是比较重要的,从第6部份开始是通过源码的角度分析了全部动画计算和内部的处理细节,和引出了对JakeWharton大神的NineOldAndroids 开源库的分析,如果你觉得太多,可以分开来看,有理解不准确的地方,欢迎大家指正。
Duration:动画的延续时间
TimeInterpolation: 用于定义动画变化率的接口,所有插值器都必须实现此接口,如线性,非线性插值器。
TypeEvaluator: 用于定义属性值计算方式的接口,有int,float,color类型,根据属性的起始、结束值和插值1起计算出当前时间的属性值
Animation sets: 动画集合,便可以同时对1个对象利用多个动画,这些动画可以同时播放也能够对不同动画设置不同的延迟
Frame refreash delay: 多少时间刷新1次,即每隔多少时间计算1次属性值,默许为10ms,终究刷新时间还受系统进程调度与硬件的影响
Repeat Country and behavoir:重复次数与方式,如播放3次、5次、无穷循环,可让此动画1直重复,或播放完时向反向播放
1.1 示例
示例1:线性动画
简单理解为匀速,下面描写了1个物体的X属性的运动。该对象的X坐标在40ms内从0移动到40 pixel,每10ms刷新1次,移动4次,每次移动40/4=10pixel。
示例2:非线性动画
简单的理解为非匀速,一样的40pixel,一样的时间,但是速率不同,开始和结束的速度要比中间部份慢,即先加速后减速
1.2、属性动画的几个重要组成部份
TimeInterpolator 实现插值器的接口,用于计算插值。
TypeAnimator 计算属性值的接口。
ValueAnimator 已实现了TimeInterpolator和TypeAnimator接口,跟踪了动画时间的相干属性,比如1个动画已完成了多长时间,当前履行动画的开始、结束或属性值。
1.3、动画的计算进程
进程1:计算已完成动画分数 elapsed fraction
为了履行1个动画,你需要创建1个ValueAnimator,并且指定目标对象属性的开始、结束值和延续时间。在调用start后,全部动画进程中, ValueAnimator会根据已完成的动画时间计算得到1个0到1之间的分数,代表该动画的已完成动画百分比。0表示0%,1表示100%,比如,示例1中,总时间 t = 40 ms,t = 10 ms 的时候是 0.25。
进程2:计算插值(动画变化率)interpolated fraction
当ValueAnimator计算完已完成动画分数后,它会调用当前设置的TimeInterpolator,去计算得到1个interpolated(插值)分数,在计算进程中,已完成动画百分比会被加入到新的插值计算中。如示例2中,由于动画的运动是缓慢加速的,它的插值分数大约是 0.15,小于在 t = 10ms 时的已完成动画分数0.25。而在示例1中,这个插值分数1直和已完成动画分数是相同的。
关于插值器的详细介绍,可以看2.3节。
进程3:计算属性值
当插值分数计算完成后,ValueAnimator 会根据插值分数调用适合的 TypeEvaluator去计算运动中的属性值。
以上分析引入了两个概念:已完成动画分数(elapsed fraction)、插值分数( interpolated fraction )。
在上面的示例2中,TimeInterpolator 使用的是 AccelerateDecelerateInterpolator ,而它的TypeEvaluator使用的是 IntEvaluator。
明白具体的进程后,我们来分析1下它的计算进程,取 t = 10ms:
进程1:计算已完成动画时间分数:t=10ms/40ms=0.25.
进程2:由于上述例子中用了AccelerateDecelerateInterpolator,其计算公式以下(input即为时间因子),经计算得到的插值大约为0.15:
这里简单说下,Interpolator接口的直接继承自TimeInterpolator,内部没有任何方法,而TimeInterpolator只有1个getInterpolation方法,所以所有的Interpolator只需实现getInterpolation方法便可。下面是AccelerateDecelerateInterpolator的源码:
参数分别为上1步的插值分数、起始值、结束值。
相信大家看到这里,全部动画的计算进程应当是非常清楚了。
第6部份的源码分析详细的介绍了这3个进程的内部实现。
由于View Animation 系统已在android.view.animation中定义了很多的插值器,你可以直接利用到你的属性动画中。 Animator虽然提供了创建动画的基本框架,但你不应当直接使用这个类,由于它只提供了很少的功能,需要去扩大才能完全支持动画。下面介绍的是1些属性动画系统中的主要类。
1.ValueAnimator
属性动画中的主要的时序引擎,如动画时间,开始、结束属性值,相应时间属性值计算方法等。包括了所有计算动画值的核心函数。也包括了每个动画时间上的细节,信息,1个动画是不是重复,是不是监听更新事件等,并且还可以设置自定义的计算类型。
全部Property Animation动画有两个步聚:
1.计算属性值
ValueAnimiator只完成了第1步工作,如果要完成第2步,你必须监听由ValueAnimator计算得到的属性值,并修改目标对象。需要实现ValueAnimator .onUpdateListener 接口,自己去处理对象的动画逻辑,比如:
2.ObjectAnimator
继承自ValueAnimator,允许你指定要进行动画的对象和该对象的1个属性。该类会根据计算得到的新值自动更新属性。也就是说上Property Animation的两个步骤都实现了。大多数的情况,你使用ObjectAnimator就足够了,由于它使得目标对象动画值的处理进程变得简单,不用再向ValueAnimator那样自己写动画更新的逻辑。但ObjectAnimator有1定的限制,比如它需要目标对象的属性提供指定的处理方法,这个时候你需要根据自己的需求在ObjectAnimator和ValueAnimator中做个选择了,看哪一种实现更简便。在下面的第3部份有重点介绍。
3.AnimationSet
动画集合,提供了1个把多个动画组合成1个组合的机制,并可设置组中动画的时序关系,犹如时播放、顺序播放或延迟播放。Elevator会告知属性动画系统如何计算1个属性的值,它们会从Animator类中获得时序数据,比如开始和结束值,并根据这些数据计算动画的属性值。
4.TimeAnimator
它其实不能直接实现动画效果,它是1个对监听者的简单回调机制,在TimeListener接口的onTimeUpdate回调方法中返回动画延续的时间与上次调用的间隔时间,没有duration、interpolation和设置值的方法等。主要是在动画的每帧的时候Notify其监听者做相应的处理。
更详细的分析和在实际使用中如何选择,请参考第3部份。
2.2 Evaluators
Evaluators 告知属性动画系统如何去计算1个属性值。它们通过Animator提供的动画的起始和结束值去计算1个动画的属性值。
属性系统提供了以下几种Evaluators:
1.IntEvaluator
2.FloatEvaluator
3.ArgbEvaluator
这3个由系统提供,分别用于计算int,float,color型(106进制)属性的计算器
4.TypeEvaluator
1个用于用户自定义计算器的接口,如果你的对象属性值类型,不是int,float,或color类型,你必须实现这个接口,去定义自己的数据类型。
更详细的介绍,请参考第5部份:使用TypeEvaluator
插值器:时间的函数,定义了动画的变化律。
插值器只需实现1个方法:getInterpolation(float input),其作用就是把0到1的elapsed fraction变化映照到另外一个interpolated fraction。传入参数是正常履行动画的时间点,返回值是用户真正想要它履行的时间点。传入参数是{0,1},返回值1般也是{0,1}。{0,1}表示整段动画的进程。中间的0.2、0.3等小数表示在全部动画(本来是匀速的)中的位置,其实就是1个比值。如果返回值是负数,会沿着相反的方向履行。如果返回的是大于1,会超越正方向履行。也就是说,动画可能在你指定的值上下波动,大多数情况下是在指定值的范围内。
getInterpolation(float input)改变了默许动画的时间点elapsed fraction,根据时间点interpolated fraction得到的是与默许时间点不同的属性值,插值器的原理就是通过改变实际履行动画的时间点,提早或延迟默许动画的时间点来到达加速/减速的效果。动画插值器目前都只是对动画履行进程的时间进行修饰,并没有对轨迹进行修饰。
简单点解释这个方法,就是当要履行input的时间时,通过Interpolator计算返回另外1个时间点,让系统履行另外1个时间的动画效果。
经过动画计算进程的第1步,会获得1个已完成时间百分比elapsed fraction,也就是getInterpolation方法的参数input。插值器,就是时间的函数,插值就是函数值。Android动画提供的AccelerateDecelerateInterolator的源码为:
截图来自:http://cogitolearning.co.uk/?p=1078,该文章也有关于Android Property Anim的介绍,有兴趣的可以看1下。
下面我们再通过AccelerateDecelerate的函数图来进1步分析。
该曲线图,表现了动画计算的两个进程:X轴是时间因子(正好最大值为1,那末每一个X轴上的值就能够看作是百分比),也就是动画计算进程的第1步所得到的值,Y轴就是相应时间的插值,就是动画计算进程的第2步。还有1步,这里没有体现出来,就是通过TypeEvaluator计算终究的属性值。
下面介绍几种插值器:
AccelerateDecelerateInterolator 先加速后减速,开始结束时慢,中间加速
AccelerateInterpolator 加速,开始时慢中间加速
DecelerateInterpolator 减速,开始时快然后减速
AnticipateInterpolator 反向 ,先向相反方向改变1段再加速播放
AnticipateOvershootInterpolator 反向加超出,先向相反方向改变,再加速播放,会超越目的值然后缓慢移动至目的值
BounceInterpolator 跳跃,快到目的值时值会跳跃,如目的值100,后面的值可能顺次为85,77,70,80,90,100
CycleIinterpolator 循环,动画循环1定次数,值的改变成1正弦函数:Math.sin(2 * mCycles * Math.PI * input)
LinearInterpolator 线性,线性均匀改变
OvershottInterpolator 超出,最后超越目的值然后缓慢改变到目的值
TimeInterpolator 1个接口,允许你自定义interpolator,以上几个都是实现了这个接口
如果这些插值器不能满足你的需求,那末你可以通过实现TimeInterpolator接口去创建自己的插值器。下面是 LinearInterpolator计算插值的方法,LinearInterpolator(线性插值器)对已完成动画百分比没有影响。
LinearInterpolator
3、利用动画
ValueAnimator类可以为1些动画指定1系列的int,float,color值。通过调用工厂方法ofInt(),ofFloat().ofObject()来获得1个ValueAnimator.
上面这段是无效的代码,由于这里根本就没有动画目标的影子,也没有在ValueAnimator的监听中获得计算得到的属性值去更新目标对象,所以不会有动画效果。
你需要为动画指定1个自定义的类型:
ValueAnimator通过MyTypeEvalutor提供的逻辑去计算1个时长为1000ms的动画在开始和结束之间的属性值,从start方法开始算起。第1块代码,对对象没有起到真实的效果,你通常希望通过计算得到的属性值去修改动画对象,但这里的ValueAnimator没有直接操作1个对象或属性。你需要在ValueAnimator中实现1个AnimatorUpdateListener监听去手动更新目标对象的属性值和处理动画生命周期中的其它重要事件,如frame的更新。当你实现了监听以后,你可以通过getAnimateValue()方法获得某1帧的动画值,然后做更新操作。更多关于Listeners的介绍,你可以参考第4部份:Animation Listeners
更加简便,动画属性会自动更新,不用再像ValueAnimator那样自己去实现更新的动画逻辑,但需要遵守1定的规则。
ObjectAnimator是ValueAnimator的子类,并且同时具有时序引擎和属性值计算和自动更新属性值的功能,使得为对象添加动画变得更加简单。因此你不再需要去实现ValueAnimator.AnimatorUpdateListener去更新动画的属性了。
ObjectAnimator的自动更新功能,依赖于属性身上的setter和getter方法,所以为了让ObjectAnimator能够正确的更新属性值,你必须遵从以下规范:
c.使用ValueAnimator代替。
2. 如果你为ObjectAnimator的工厂方法的可变参数只传递了1个值,那末会被作为动画的结束值。因此,你的目标对象属性上必须要有1个getter方法,用于获得动画的起始值。这个获得方法必须使用get()的格式。例如,属性是foo,就必须有1个getFoo方法。
targetObject.setPropName(float) 和targetObject.getPropName(float) :
4. 根据动画的目标属性或对象不同,你可能需要调用某个View的invalidate方法,根据新的动画值去强迫屏幕重绘该View。可以在onAnimateonUpdate()回调方法中去做。比如,对1个Drawable的色彩属性进行动画,只有当对象重绘本身的时候,才会致使该属性的更新,(不像平移或缩放那样是实时的)。1个VIew的所有setter属性方法,比如setAlpha()和setTranslationX()都可以适当的更新View。因此你不需要在重绘的时候为这些方法传递新的值。更多关于 Listener的信息,可以参考第4部份Animation Listeners。
简单总结下:
当你不希望向外暴露Setter方法的时候,或希望获得到动画值统1做处理的话,亦或只需要1个简单的时序机制的话,那末你可以选择使用ValueAnimator,它更简单。
如果你就是希望更新动画,更简便的,可使用ObjectAnimator,但你必须有setter和getter方法,并且它们必须都是标准的驼峰式(确保内部能够调用),必须有结束值。
根据需要,不需实时更新的动画,需要你自己去强迫更新。
很多时候,你需要在1个动画的开始或结束点去播放另外一个动画,Android系统允许你绑定多个动画到1个AnimatorSet中,因此你可以指定这些动画是不是同时启动或有序或延迟进行。你也能够相互内嵌AnimatorSet。下面的代码来自Google Sample弹力球Sample,按顺序播放了以下动画:
1、播放 bounceAnim.
2、同时播放 squashAnim1, squashAnim2, stretchAnim1, and stretchAnim2
3、播放 bounceBackAnim.
更多细节,你可以参考APIDemo,APIDemo在大家的SDK中都有,直接导入便可。
你可以通过以下监听器监听动画进程中的重要事件:
Animator.AnimatorListener
onAnimationStart() - 动画启动时的回调
onAnimationEnd() -动画结束时的回调
onAnimationRepeat() - 动画重复本身时候回调
onAnimationCancel() - 动画被取消的时候回调,1个动画取消的时候也会调用onAnimationEnd方法,而不斟酌动画是如何结束的。
onAnimationUpdate() :动画的每帧都会调用该方法,监听该事件去使用ValueAnimator计算得到的值。通过getAnimatedValue方法可以获得当前的动画值。如果你使用 的是ValueAnimator,实现该监听就是有必要的了。
根据动画的属性的实际情况,你可能需要根据新的动画值去调用某个View身上的invalidate方法去强迫刷新某1个区域。这1点和ObjectAnimator中的第4点相同。
如果你不想实现Animator.AnimatorListener接口的所有的方法,你可以继承AnimatorListenerAdapter类,而不用去实现Animator.AnimatorListener接口。
AnimatorListenerAdapter类提供了1些空的实现,你可以选择性的覆盖。比如API中弹力球sample,创建了1个AnimatorListenerAdapter,而只实现了onAnimationEnd方法。
如果你想添加1种动画系统中没有的计算类型,就需要自己通过实现TypeEvaluator接口去创建自己的evaluator。Android系统可以辨认的类型是:int,float或color。对应的java类分别为 IntEvaluator、 FloatEvaluator、 ArgbEvaluator 。
我们再来看1下IntEvaluators的源码:
大家可以看到,3种计算器都是线性的,且情势都为: result = x0 + t * (v1 - v0)。
如果你的数据类型不是float,int,或color类型,那末你就需要自己实现TypeEvaluator,并实现evaluate方法,根据自己的数据结构计算属性值。
代码家的开源动画库AnimationEasingFunctions就是根据1个函数库http://easings.net/zh-cn做出来的,每一个不同的动画效果就是复写了evaluate方法,依照不同的函数计算属性值,从而到达了相应的动画效果。大家可以自己去看AnimationEasingFunctions的源码,在理解了1.3动画的计算进程后,再去看,就非常清晰了,关键地方就是这个evaluate方法根据不同的函数做了处理。
TimeInterpolator和TypeEvaluator的区分
不知道大家弄明白TypeEvaluator和TimeInterpolator没有,反正当时我刚看的时候,有些迷糊,不知道该如何具体的使用。
当时分析了代码家的AnimationEasingFunctions开源项目,发现它都是在TypeEvaluator中定义函数,而不是在TimeInterpolator中。
我当时很困惑,我的想法是在TimeInterpolator 中定义插值函数,而在Evaluators的evaluate方法只是简单的处理。比如系统提供的Evaluators那样,简单的进行线性运算便可,我当时对Evaluators的理解是:它只是为了扩大1种数据类型,比如系统提供的IntEvaluator、FloatEvaluator,它们内部计算只是简单的线性计算,只是类型不同而已。后来实在不太明白,就向代码家请教了下,代码家的答复:
TypeEvalutor的evaluate方法接收的fraction究竟来自于哪里?
我觉得这个fraction非常重要,由于它连接了动画值计算的第2步和第3步,所以弄清楚它究竟是甚么,对后续第3步属性值的计算非常重要。这里也在同1封邮件中向代码家请教过,代码家的答复是从第1个参数就是从 getInterpolator得到的。但是自己1直觉得哪里不对,后来经过Debug得出来了1些结果,也就是第6部份的来由,如果当初没有深入探索下去,就没有第6部份源码分析这1块,而终究收获很多,并且弄清了NineOldAndroids的实现原理。
首先说明下,在测试的时候,使用的是ObjectAnimator.ofFloat( )工厂方法,值类型为Float,所之内部逻辑使用了FloatKeyframeSet类(关于FloatKeyframeSet后面有详细的介绍,这里只需知道在该类里肯定了传入Evaluator的fraction)的getFloatValue方法,动画的每帧都会履行这个方法,这里也是动画计算进程的第3步产生的地方,先计算得到1个中间值,然后传递到evaluator中的evaluate方法中去计算得到终究的属性值。该方法中,对不同参数个数的情况进行了不同的处理,具体看源码:
FloatKeyframeSet.java
可以看到这里1共处理了4种情况:
mKeyframeNums == 2
fraction <= 0
fraction >= 1
fraction在(0,1)之间且mKeyframeNums != 2
我们先看看keyframe.getFraction()获得到的是甚么值:
PropertyViewHolder
这里有个从角标1开始的for循环,循环调用Keyframe.ofFloat(fraction,value)工厂方法,创建Keyframe。第1个keyframe的fraction为0,这是默许的。
而其它关键帧fraction的计算方式我们可以看到:i / (numKeyframes⑴), numKeyframes为用户传入到ObjectAnimator.ofFloat(Object target ,String PropertyName,float ...values) 方法的可变参数values个数。注意我们这里的value参数是动画运动的关键帧,和之前所说的动画运动的每帧是不同的。运动进程中的每帧是关键帧之间的那1部份,这部份是实时的,而关键帧就是1个个用户指定的属性值,希望在某个时间点(上述已计算完成),到达的属性值。
mKeyframeNums = 2
返回的就是直接从参数中获得到的fraction,而这个fraction就是从通过ValueAnimator的Interpolator获得到的。所以在这类情况下,正如代码家的回复1样。
下面我们看1下源码中对getInterpolation()方法的注释:Value可以大于1或小于0。
其实当初分析的时候,有1个误区,就是我所认为的evaluate中的fraction必须是[0,1]范围内的1个值,这样才合适作为1个比例值,所以对getInterpolation(input)方法返回的值,在mKeyframeNums = 2 的时候,直接传递给Evaluator的evaluate方法,1直很困惑,最后才明白,getInterpolation(input)的值,其实不受束缚的,完全可以由你自定义的插值函数来控制,终究计算得到的属性值,也不1定就比用户传入到ofFloat()中的Value小。事实确切是这样,动画的运动轨迹,是可以在你的指定的属性值上下波动的。
我们再看其它3种情况的处理:
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
选择的是第1帧(从上面的赋值来知道,第1帧的fraction为固定值0)和第2帧
prevkeyframeFraction = 0,nextKeyframeFraction = 1 / 2:
fraction >= 1
由mNumkeyframes⑴,mNumkeyframes⑵,可以知道,这里获得的就是倒数第1帧和倒数第2帧。
prevkeyframeFraction = 1/2 ,nextKeyframeFraction = 1:
mKeyframeNums != 2(或==1,内部已处理为2)且在[0,1]范围内
上面逻辑中有这么1行代码: if (fraction < nextKeyframe.getFraction()) {...}
那末我们可以知道,这个elapsed fraction是某两关键帧区间的elapsed fraction,落到了某1关键帧和下1关键帧区间里。如图该fraction落在了1/2和1之间的区域:
上面更加清晰的知道,fraction其实不1定在{0,1}内,也多是该区间外的1个值,只是系统为了更接近这个fraction,在做处理的时候,选择两个相近的fraction进行计算,得到1个internalFraction传递给Evaluator的evaluate方法去计算属性值。
因此这里可以解决我上面疑问了,evaluate接受的fraction分为两种:
当用户传入的属性值是2个的时候:是getInterpolator()返回的fraction。
其它情况又分为3种,fraction>=1 和 fraction<=1的取值是固定的两关键帧,0
兜了1大圈,其实就是为了弄清楚这个fraction究竟是个甚么值,现在明白了,其实只要知道这个fraction不1定是{0,1}之间的值,就OK了,就没有甚么疑问了。
小结:
TypeEvaluator: 定义了属性值的计算方式,有int,float,color类型,根据属性的开始、结束值和插值1起计算出当前时间的属性值,终极方法,全部计算进程的结尾。
TimeInterpolation: 插值器都必须实现的接口,定义了动画的变化率,如线性,非线性。
ValueAnimator与ObjectAnimator:二者都可以进行属性动画,但是ObjectAnimator更加简单,不用去做更新属性值的计算,但是必须要提供标准的setter和getter方法,让ObjectAnimator能够获得到属性值。
以上部份为PropertyAnim的核心部份,主要分析已介绍完了,如果1时消化不了,可以将源码的分析先放1放,回过头来再看,也没问题,如果你希望1气呵成,1下看完自然是极好的 :)
6、通过源码的角度来分析全部动画的全进程
先说明1下全部进程的分析是基于Jake Wharton的NineOldAndroids的,但除初始化和动画的更新不同,其它的整体逻辑和思路是1样的,只是有些细节实现不同,毕竟大神不可能完全copy过来,有自己的代码习惯。所以大家不用担心和Android系统的源码有太大出入,而对NineOldAndroids的原理分析部份,侧重谈到了实现原理和初始化和动画更新部份与系统动画的不同的地方。
初始化进程,就是由ObjectAnimator.ofFloat();方法开始所做的1系列工作