和之前的文章一样,没有使用代码进行讲解,所以读者阅读起来可能有点吃力。我建议阅读的时候,结合源码,效果更佳。文章必定会有疏漏的地方,读者在阅读过程如发现,请务必指正,不甚感激!
动画是什么?
这或许是个简单的问题,很多人都会回答说“动画就是一帧帧静态画面的连续播放”。这样的回答表明,动画的组成元素是每一帧画面。我们看到的2D动画就是这样。然而我们发现在电影院,2D动画几乎已经绝迹,普遍都是画质更为逼真,人物表情更为丰富的CG动画。制作CG动画有一个重要的步骤就是--建模,比如人物,花草,建筑物等等都是被独立看待的一个个模型。我们以CG动画的角度看,动画的组成元素是一个个独立的模型。每个模型都有本身的属性,比如头发的弧度,手臂的位置,瞳孔的颜色等等。当我们把动画以帧的方式进行分割,实际上就割裂了属性变化的连续性。而CG动画则是以事物属性为关注点,并且注重属性变化的连续性,所以效果就更加逼真灵动。
从2D动画到CG动画,是技术的进步,也是认知的提升。Android动画框架也经历了这样的转变。
Android早期提供的动画框架被称为帧动画,就像2D动画一样,所以也有着2D动画的缺点--动画效果卡顿。这使得Android在3.0推出了全新的动画框架--属性动画。属性动画与CG动画的核心理念是一样的--所动画的是属性,而不是帧。Androd官方把属性动画框架的开发称之为--黄油计划,原因显而易见,就是让动画像黄油一样顺滑。
我在此谈的就是属性动画。
动画回调何时执行?
在回答这个问题之前先谈谈主线程。主线程又称UI线程,主要作用是处理交互事件,执行界面绘制等功能。每个人都能拿这个回答去搪塞别人,回答问题的人未必在头脑中建立了主线程运转机制的模型。线程交互的基本模型就是生产者-消费者模型。在这个模型中工作线程将生产的Runnable,不断发送到主线程供其消费。主线程的Run方法存在一个死循环,在这个死循环中,它不断处理别人生成的Runnable.。投递给主线程的Runnable一般分为两类。一类是系统产生的事件,如交互事件,四大组件的生命周期回调;另一类事件是程序自身产生的--那些post系列方法发送的Runnable,动画事件回调,界面绘制事件。四大组件的生命周期回调我们不谈及,他是由系统操控的。这些Runnable就是胡乱的塞到主线程的消息队列中的吗?显然不是,绝对不是!
问题来了,这些Runnable如何在主线程中进行组织?
所谓组织就是给这些Runnable排个序,排序标准有两个:一是这些Runnale之间固有的先后顺序,比如交互事件的处理就应该在绘制事件前面,因为我们只有计算了手移动了多少距离,才能决定屏幕中的控件移动多少距离;二是以时间排序,比如postDelayed方法就会延迟Runnable的执行。这些事件在主线程的处理顺序是:交互事件回调--动画事件回调--界面绘制。Android系统每隔16ms发出VSYNC信号,接收到VSYNC信号,主线程会依次执行交互事件回调,动画事件回调,最后触发UI绘制。在Android中实现Runnable调度组织功能的是Choreographer,它为以上三类事件的分别维护了一个列表。
属性动画如何将动画回调事件交给Choreographer?
我能想到最简陋的动画效果实现就是,一个放映员不断切换幻灯片,只要放映员切换速度足够快,也能达到"动画"的效果。这里有个重要的角色就是切换幻灯片的放映员。Android的属性动画也需要这样一个放映员,我们称这个放映员为动画引擎。
Android属性动画的引擎只有一个,所有的动画都由这个引擎调度。这样做的好处是所有动画分享相同的时间,那么动画之间就能同步。在Android中动画引擎的实现类是AnimationHandler--组织编排所有动画的执行(每个动画的启动时间不一样,另外有的动画会延迟执行),根据动画时间计算动画值。AnimationHandler会拿到Choreographer的一个实例,并将自己放置到Choreographer所维护的动画事件列表中,随着每一次VSYNC信号的到来,而得到执行。
为什么属性动画会顺滑?
之前提到了Android系统会每隔16ms发出VSYNC信号。为什么是16ms?因为人类视觉能接受到画面变化的时间就是16ms,当大于16ms,人就会感觉到画面不连续,我们称为卡顿,所以Android会每隔16ms刷新一次画面。一般的,我们在动画回调中修改了View的某些属性,然后紧接着在View的绘制中应用了了这些属性,这一切都是在这16ms内完成的,因而我们感觉到动画效果是顺滑。