前几天与小伙伴交流,他做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
回调的时候,没有完整的分析。