安卓动画是如何动起来的

347 阅读6分钟

andorid动画分为三种:

帧动画:顺序播放多张图片实现动画效果。

补间动画:通过指定开始帧和结束帧,中间帧计算补齐。支持透明度、旋转、平移、缩放四种。通过设置给view启动,view在draw的过程中计算当前帧,应用于Canvas(或者RenderNode),实现当前帧的绘制。当前帧绘制完成后,调用invalidate()继续请求下一帧绘制,直到动画结束。补间动画的本质只是在绘制的时候更改了画布,不会改变view的属性,所以它的一个典型问题就是显示位置变了,点击位置没变。

属性动画:通过改变控件属性实现动画,可以改变控件任意属性,没有补间动画的限制,并且因为直接改变属性,不会有补间动画那种显示和实际属性不一致的问题。

在开发过程中,经常使用到,但都只是调用,却不清楚它的原理是什么,今天学习一下动画是如何动起来的,主要是属性动画。

动画启动

属性动画的启动代码最后都是走到了ValueAnimator.start()方法,其中除了初始化的参数设置,就是调用 addAnimationCallback()方法设置回调,然后回调处理动画的每一帧。

1667122850923.png

addAnimationCallback()里面的操作就是透传到了这个 AnimationHandler里面,AnimationHandler用做处理所有活动Animator共享的定时脉冲,线程单例,而这个线程就是 Animator.start()方法调用的线程。

1667123016755.png

接着看 AnimationHandler.addAnimationCallback(),方法注释已经介绍将回调绑定到下一帧的回调,方法也比较简单,第一个判断看起来像是如果没有初始化就初始化,第二个和第三个判断就是根据不同延迟把回调存起来。

1667123535711.png

AnimationHandler

继续看就要看下 AnimationHandler的结构,上面的 addAnimationCallback()涉及到多个东西,根据这个方法一个一个看。

Provider

首先第一个就是 getProvider(),两个方法也很明确,自己设置或者使用默认,而他的注释也写的很清楚,它用作提供定时脉冲信号。

1667124528894.png

MyFrameCallbackProvider本身也就是实现了一个接口,将所有的方法传递给 Choreographer处理,而Provider本身的接口注释也写的很明确,它是为了更好的可测试性,使用的时候只需要使用默认的 Choreographer处理即可,只有需要调试的时候可以设置自己的脉冲信号。

Choreographer在之前绘制的相关文章也有涉及到,它接收手机的垂直同步刷新信号。Android界面在 Choreographer的引导下能有条理的显示出来,宛如舞者在屏幕上翩翩起舞一般,而这也正是 Choreographer本身翻译过来是编舞者。这里先挖个坑,后面来学习 Choreographer的相关知识,这里只是给下这些方法的注释。

首先这里有多个回调,而 Choreographer本身也是线程单例,而这里的线程同样也是 Animator.start()调用的线程,而这个线程也是最终处理回调的线程。

postFrameCallback:下一帧运行回调,运行之后移除

postCommitCallback:下一帧运行回调,会在下一帧处理页面绘制之后运行,运行之后移除

getFrameTime:获取当前帧开始时间,可能页面绘制用了多个帧

getFrameDelay:获取帧间隔时间

setFrameDelay:设置帧间隔,可以设置期望的动画间隔,可能有误差

1667124629196.png

FrameCallback、AnimationFrameCallback

拿到Provider之后,首先调用的就是 postFrameCallback()绑定下一帧回调,这里的 FrameCallback就是AnimationHandler中的处理逻辑,而其中也就是将事件通过 AnimationFrameCallback传递出去给 ValueAnimator使用。

AnimationFrameCallbackFrameCallback基本相同,多了一个处理 commitCallback的回调,它的参数是回调执行帧的开始时间,也就是开始帧加上绘制消耗掉多个帧的时间,这样可以正确计算动画时间。

1667213328505.png

1667213337223.png FrameCallback的实例,调用 doAnimationFrame()传递回调,如果回调没有清空,重复绑定下一帧回调。

1667293895002.png

doAnimationFrame()三种回调同时处理,第一种是直接回调,第二种延迟启动,第三种 commitCallback

首先是从 mAnimationCallbacks取出 start()绑定的 AnimationCallback,然后调用 isCallbackDue(),会在 mDelayedCallbackStartTime查询,前面 AnimationHandler.addAnimationCallback()中如果有延迟,就会往这个里面添加。查不到或者时间到了,就调用 callback.doAnimationCallback()将事件传递给 ValueAnimator

然后还会在 mCommitCallbacks中查询这个callback,就会调用 postCommitCallback()绑定下一帧的延迟回调。

最后的 cleanUpList()用作移除脏数据,调用 RemoveCallback()时候会标记脏数据,在帧回调的时候清空回调。

1667295884363.png

ValueAnimator

Handler流程大致跑完之后,处理逻辑到了 ValueAnimator.doAnimationFrame()中,先看注释处理一帧和开始时间,忽略初始化的处理,直接到最后两个方法,animateBaseOnTime()处理当前帧逻辑,endAnimation()处理结束逻辑。

1667546042812.png

animateBaseOnTime()中,初步计算了 fraction,这个值是当前时间在总时间的百分比,也就是当前帧的动画进度。并且这里的计算包括了计算总体时间进度,并且映射到重复动画、反向动画,计算出了当前帧的最终进度 currentIterationFraction,然后将其继续传递到 animatorValue()继续处理。

在这个计算过程中,顺便也计算了当前动画是否已经到了或者过了最后帧,表示动画已经结束,如果结束了会触发上面的 endAnimation()方法,移除回调,结束动画。

1667547100830.png 最后动画的处理到了 animateValue()animateValue()做了三件事: mInterpolator又做了一次 fraction的过滤。mValues计算当前值。mUpdateListeners通知回调。

Interpolator应该都很熟悉,逻辑上说就是将值映射一次,功能上就是将线性的时间变化映射为对应情况的非线性变化,比如加速减速或者从高处下落的速度变化,让动画看上去更舒服。

mValues本身对应就是 PropertyValuesHolder,这个类包含有关属性的信息,以及属性在动画期间应该使用的值,在 ObjectAnimator中也直接使用它将属性值设置给对应组件。

AnimatorUpdateListener动画回调,如果不使用 PropertyValuesHolder可以在这里做一些自定义的操作。

执行到这里,要不通过 PropertyValuesHolderObjectAnimator操作控件属性。或者通过 AnimatorUpdateListener去自定义操作属性。

1667554761126.png

到这里将 ValueAnimator的执行串了起来,动画启动之后,绑定到垂直同步定时脉冲上,每次脉冲检查是否已经到了执行时间(延迟启动),启动之后根据每一次脉冲的时间、正反向执行、插值器计算出当前的动画进度百分比,最后执行到 PropertyValuesHolder或者 AnimatorUpdateListener将百分比再设置给对应属性。