ValueAnimator学习笔记
关于ValueAnimator学习的一些心得和记录。
结论先行:属性动画ValueAnimator是根据不断改变一个控件的属性达到动画的效果。
那么在一个属性动画执行了start()方法之后其做了一些什么事情呢,又是如何以及何时一帧一帧地获取屏幕刷新信号然后改变控件的属性,内部又进行了什么样的计算呢。
前段时间做了一个属性动画的小demo,借此机会对属性动画(ValueAnimator)进行一下总结。
这次使用的是ValueAnimator实现三个图标轮转的一个动画,下面开始介绍一下属性动画的运行原理。
首先看一下基础的使用:
ValueAnimator.ofInt(0,100).apply {
duration = 1000
addUpdateListener {
binding.test.translationX = it.animatedValue as Float
}
start()
}
这样就得到了一个执行了1s的平移动画,根据updateListener的回调方法获取其中的值并以此更新控件的值。
属性动画是什么时候开始执行,如何执行的,真正生效的地方在哪里,怎么持续 1s 的,内部是如何进行计算的。
首先:android底层是会定时刷新一次屏幕,也就是发出一个屏幕的刷新信号,我们的程序接收到这个屏幕刷新信号时,就会计算屏幕的数据,也就是测量、布局、绘制这三大流程。要想获得屏幕信号的刷新,首先就需要监听屏幕信号刷新事件,跟踪源码并且查看一些相关的博客后明白了是Choreographer这个类实现了程序和底层屏幕刷新信号做的交互。
从start()开始:
ValueAnimator#start()
这个方法很简单,继续调用start(boolean)这个方法,看一下这个方法:
ValueAnimator#start(Boolean)
貌似这里进行了部分变量的初始化工作,比如start、pause、running等状态的变量,其中有一个StartAnimation比较符合题意,进入看看:
ValueAnimator#startAnimation()
然后这里有两个 方法,initAnimation() 以及 notifyStartListeners(),分别进去看一下:
ValueAnimator#initAnimation()、ValueAnimator#inotifyStartListeners()。
其中initAnimation也是对一些变量的初始化、notifyStartListener则是对每一个监听者进行回调,emmm,好像已经走到尽头了,怎么到头来什么都没看到的感觉。但是回头再看看,好像错过了许多!
错过的函数:addAnimationFrameCallBack,这个名字有帧有回调,与我们的需求十分像。
AnimationHandler
首先看一下AnimationHandler这个类,是一个单例类,内部有一个mAnimationCallBacks这个回调列表。
AnimationHandler#addAnimationFrameCallBack()
addAnimationFrameCallBack有两个参数,一个是传入this也就是valueAnimator自身,另一个是delay,当前没有设置,为0。当当前handler中的mAnimationCallBacks大小为0的时候,就会创建一个MyFrameCallBackProvider类进行操作,并且调用其中的postFrameCallBack方法,并且传入mFrameCallBack,
而走进这个类中可以看到,完全就是使用Choreographer(编舞者)来实现自身的每一个函数:
MyFrameCallBackProvider
然后继续走进这个函数,接着是:
Choreographer#postFrameCallBackDelay()
Choreographer#postCallBackDelayedInternal()
其中的参数CALLBACK_ANIMATION用来区分动画事件列表,其中还有runnabel列表,继续走下去调用了scheduleVsyncLocked()
Choreographer#scheduleVsyncLocked()
这是一个native函数,猜测是在这个函数中向底层监听了屏幕刷新事件(网上都说是)。
现在,向底层添加监听已经清晰了,那么当屏幕刷新事件过来之后,又会发生什么事情呢,
刚刚上面传入的mFrameCallBack就是一个Choreographer.FrameCallBack的实例,根据Choreographer已经向底层监听了刷新事件,传入的也是他,那么我们观察一下他唯一的回调方法:
Choreographer.FrameCallBack#doFrame()
首先执行了了doAnimationFrame这个方法,其中的frameTime应该就是本次获得到的时间。接着handler判断其中 mAnimationCallBacks列表大小,如果还有callBack的话就继续将自己放入Choreographer让其帮自身监听屏幕刷新时间,那么就此猜测,如果这个列表为0的时候,就是当前所有动画事件都停止的时候。并且这里也表明动画的执行这个过程是需要handler不断向Choreographer传递监听,Choreographer不断向底层进行监听的一个持续过程。
AnimationHandler#doAnimationFrame(frameTime)
这里就很明朗了,这里会将刚刚valueAnimator将自身加入的一个回调列表中,handler将回调这个列表中的每一个值,当然需要判断其是否设置delay。这里还有一个cleanUpList()方法,应该就是刚刚说的,动画执行完之后会将自己取消注册,让handler不必再向Choreographer注册监听事件。
AnimationHandler#cleanUpList()
可以看出来,handler将mAnimationCallBacks中已经为null的值删除,那么应该是动画执行结束之后会将自己在handler中的值赋为null。
ValueAnimator#doAnimationFrame()
终于回到了valueAnimator,获取handler实例,主要是调用了三个方法,startTime的赋值与是否设置mSeekFraction,也就是开始比例有关,如果有设置,那么会从设置的默认时间开始动画(setCurrentPlayTime可以设置)。getScaledDuration方法就是获取其动画持续时长,mSeekFraction就是一个比例,取值0-1,f用来设置动画的起始时间。
AnimationHandler#addOneSHotCommitCallBack
//todo
ValueAnimator#animateBasedOnTime(currentTime)
这个方法功能和其名字相同,主要是用来计算当前的值然后进行动画。首先是根据currentTime与动画的startTime来计算当前动画的执行进度,也就是当前的fraction,
ValueAnimator#animateValue(fraction)
这里使用了两个插值器,一个是时间插值器,一个是值插值器。也就是说刚刚计算的fraction只是作为线性的fraction,真正使用的fraction是通过mInterpolator计算出来的,比如如果想要开始和结束的动画比中间动画执行慢,那么就可以根据这个插值器计算相对应的fraction。举个例子,一个动画持续时间为1000ms,那么在距离开始时间为100ms的情况下,其fraction就是1/10,但是经过mInterpolator计算之后其fractrion可以为1/20,(如果计算值是线性的话)这样就可以达到刚开始慢速,中间提速的效果。
PropertyValuesHoler#calculateValue(fraction)
接着是mValues.calculateValue(fraction),刚才得到的fraction还需要经过值的计算才得到最后的值,这个mAnimatedValue就是我们回调中使用的值,分析其中的getValue(),以IntKeyframeSet为例。
IntKeyframeSet#getValue
KeyFrame也就是关键帧,其中IntKeyframeSet最后会调用getIntValue,如果是两帧:
IntKeyframeSet#getIntValue(fraction)
IntKeyframeSet#getIntValue(fraction)
如果是多个关键帧,直接上例子,ValueAnimator.ofInt(0,100,0,-100),那么其关键帧的计算就是(fraction,value) =>(0,0)、(1/3,100)、(2/3,0)、(1,-100)。可以理解为前1/3时间内从0-100,中间的1/3时间内从100-0,最后的1/3时间内从0 - -100。
那么获得了关键帧之后,如何对获取最后计算的值呢,如果是只有两个关键帧的话,直接就按照计算就可以。
如果是多个关键帧的话,根据计算当前fraction在其当前属于的关键帧之间的权重来计算。还是刚刚的例子,比如现在fraction是1/4,也就是动画已经执行了1/4,那么其属于在第一个关键帧与第二个关键帧之间,系统的找法也就是根据一个for循环来寻找。那么其在第一个关键帧与第二个关键帧来说,已经走了这两个关键帧之间的(1/4)/(1/3) = 3/4,如果采用线性计算的话,当前的值就是100 * 3 /4 = 75,如果自定义了mEvalutor则根据自定义的规则计算。
将刚刚获取到最后的值回调给监听者,然后监听者根据该值进行一系列的计算使用修改用以修改控件的属性,达到动画的效果。
ValueAnimator#endAnimation()
AnimationHandler#removeCallBack(AnimationFrameCallBack)
猜想正确,将自身请求从mAnimationCallBacks列表中移除,然后handler中会将其赋为null,在cleanUpList方法中将其删除。
那么,updateListener获得值之后,将属性更新,再经过测量、布局、绘制的过程,一个动画就形成了。
仅仅是对动画的执行过程有一个初步的认识,欢迎指正。