前几天与小伙伴交流,他做Android 时间不长,做了很多
View了,觉得View已经全部掌握了。然后我以一个过来人的身份(臭不要脸)问他属性动画的原理,然后他支支吾吾的说就是通过反射设置的啊…
自己曾经也看过属性动画的源码,但是没好好的整理一份,这次趁这个机会还是整理一番。
首先属性动画中是通过一直调用相应的方法来改变View中相应的值。如下面的代码,使用ValueAnimation(使用Handler循环发消息可能更好理解)来构造一个动画在onAnimationUpdate中手动去改变改变Alpha 和TranslationX 的值也是可以实现动画的。所以得出来的结论是属性动画的重点不是通过反射改变值了(管它是通过什么呢),而是如何去控制设置动画的时机以及如何去控制绘制的。
  | 
  | 
源码分析
就用上面的例子来分析源码,不过只看TranslationX 一个属性了(多个属性最终其实是一样的)。所以代码先可以缩减下。
  | 
  | 
代码的作用分成了两部分,ValueAnimator的构造和ValueAnimator#start,所以看源代码也从这两个方面入手。
ValueAnimator 构造过程
创建PropertyValuesHolder对象
  | 
  | 
直接new 一个FloatPropertyValuesHolder对象(这里根据需求可能是IntPropertyValuesHolder之类的)
  | 
  | 
FloatPropertyValuesHolder构造函数里面第1行是直接复制语句,关键是下面的setFloatValues。
  | 
  | 
关键是调用基类的setFloatValues。
  | 
  | 
主要是构造了一个Keyframes对象。
  | 
  | 
Keyframe中保存的是动画中某一帧的开始时间fraction(0.0f-1.0f之间的值)和相应的值,相当于保存了动画的运动轨迹,比如最上面的ofFloat("translationX", 0.0f, 100.0f, 50.0f),动画会保存轨迹是0.0f到100.0f最后到50.0f,而不是直接0.0f到50.0f。
第6行创建了FloatKeyframe的数组,分了两种情况,numKeyframes == 1时只会设置keyframes[0]和keyframes[1]分别代表的是动画的起始位置和结束位置;numKeyframes > 1时,则将动画均分成numKeyframes - 1份,分别保存着起始时间和值的信息。最后就是构建了一个FloatKeyframeSet对象,最后里面全是赋值的逻辑,代码就不再贴出来。
总结:到此一个PropertyValuesHolder对象就创建出来了,主要做的事情就是记录了一组数据,记录着动画的轨迹信息。
创建ValueAnimator
  | 
  | 
首先创建ValueAnimator对象,然后将步骤1中的值设置进去。
  | 
  | 
将步骤1中的信息保存在ValueAnimator的mValues 和mValuesMap 中。
总结:到此动画已经构造完成,并且相应的信息保存在ValueAnimator中。
执行动画
ValueAnimator#start(),直接调用了ValueAnimator#start(false)
  | 
  | 
AnimationHandler直接看注视是通过Choreographer来实现定时回调的。Choreographer可能讲的不是很清楚,简单理解每当绘制一帧的时候Choreographer都会有一个回调执行。
直接看到24行创建了一个AnimationHandler然后执行了addAnimationFrameCallback,mStartDelay == 0所以还会执行startAnimation,mSeekFraction == -1所以还会执行setCurrentPlayTime(0);。
addAnimationFrameCallback执行的过程
  | 
  | 
最开始mAnimationCallbacks.size() == 0所以会执行getProvider().postFrameCallback(mFrameCallback)和mAnimationCallbacks.add(callback)
  | 
  | 
第6行执行到了Choreographer的方法,继续跟下去,中间跳过几个方法,直接到最终调用的地方。
  | 
  | 
第10行的判断因为delayMillis == 0,所以直接会执行scheduleFrameLocked。
  | 
  | 
这里的调用会直接执行到第11行,最后会执行到nativeScheduleVsync(mReceiverPtr),是一个native 方法,在此打住,后面回调到Choreographer.FrameDisplayEventReceiver#onVsync。
  | 
  | 
第24行代码表示将在timestampNanos / TimeUtils.NANOS_PER_MS时会执行Handler抛出的Message,最终会执行到Choreographer#doFrame。
  | 
  | 
执行到doCallbacks。
  | 
  | 
第7行得到的是callbacks的callbackType是CALLBACK_ANIMATION,执行第22行。
  | 
  | 
最终最终会执行到4行。这个action 就是最开始postFrameCallback传入的,绕了一大段现在绕回来了。
总结:这个过程相当于设置了一个Choreographer的回调,然后调用VSync 相关方法,最后回调回来调用最初设置的Choreographer回调。
回到AnimationHandler查看回调的执行。
  | 
  | 
执行doAnimationFrame,然后判断以后继续执行postFrameCallback,相当于逻辑再走一遍。
  | 
  | 
直接看第11行代码,回调到ValueAnimator#doAnimationFrame
  | 
  | 
第4行获取到的是绘制该帧的时间。然后执行animateBasedOnTime
  | 
  | 
计算出时间过去的百分比,然后执行到第6行。
  | 
  | 
第3行通过Interpolator计算出当前动画完成的百分比,第7行通过动画执行的百分比计算相应属性的值修改最开始保存的值。第12行回调onAnimationUpdate。主要看calculateValue,流程是PropertyValuesHolder.FloatPropertyValuesHolder#getFloatValue,最后执行到FloatKeyframeSet.getFloatValue
  | 
  | 
为了简单只看mNumKeyframes == 2的情况,第5-10行计算出开始值、结束值以及属性的变化大小。第14行去判断有没有估值器,没有直接线性变化了,有则执行第17行,会使用Evaluator 和当前动画执行的百分比来计算属性的值。
总结:到此完整的从构造到通过VSync 回调,再到最后修改值的流程走了一遍。
什么,没有看到反射调用??这个可以去看看ObjectAnimator.animateValue,这里不展开了,嘿嘿。
startAnimation执行过程
主要是标志动画开始,初始化一些属性,比如默认的Evaluator。
setCurrentPlayTime(0)执行过程
主要是用来设置动画的起始位置,这里的起始位置是0,所以不在展开了。
总结
所以属性动画的原理是什么呢?随着时间变化计算出动画完成的百分比,然后根据动画完成的百分比再计算出属性的最终值,最后设置属性,这个流程中还包含了通过Interpolator计算动画完成的百分比,通过Evaluator计算出最终值。内部还是比较复杂的,特别是通过Choreographer回调的时候,没有完整的分析。