概要
android的动画可以分为三种:补间动画、帧动画和属性动画。
补间动画(Tween Animation)
通过对场景里的对象不断做图像变换(平移、缩放、旋转、透明度)从而产生动画效果,它是一种渐进式动画,并且支持自定义。
帧动画(Drawable Animation)
通过顺序播放一些列图像从而产生动画效果,可以简单理解为图片切换动画,但是在图片过多过大时会导致OOM。
属性动画(Property Animation)
通过动态改变对象的属性从而达到动画效果,属性动画是API 11的新特性,在低版本需要通过兼容库来使用它。
补间动画的实现原理
1、基础使用
补间动画的作用对象是View,他支持四种动画效果,分别是平移动画、缩放动画、旋转动画和透明度动画。补间动画的四种变换效果对应这Animation的四个子类:TranslateAnimation、ScaleAnimation、RotateAnimation和AlphaAnimation,这四种动画既可以通过XML来定义,也可以通过代码来动态创建。
补间动画的使用步骤
- 创建一个animationSet对象
- 增加需要创建对应的animation对象
- 根据项目的需求,为animation对象设置相应的数据
- 将animation对象添加到animationSet中
- View控件开始执行animation
2、自定义补间动画
自定义补间动画是一件既简单又复杂的事情,派生一种新动画只需要继承Animation这个抽象类,然后重写它的initialize和applyTransformation方法,在initialize方法中做一些初始化工作,在applyTransformation中进行相应的矩阵变换即可,很多时候需要采用Canvas来简化矩阵变换的过程,而自定义补间动画的过程主要是矩阵变化的过程,矩阵是数学上的概念。
3、工作原理
android的补间动画中最重要的类是Animation类,一个抽象类,是所有动画的基类,它定义了Animation的公共属性和方法。属性中最重要的是:AnimationListener和Transformation,AnimationListener是动画监听器,监听动画的开始,执行过程,结束,可以实现一些自己的逻辑,Transformation是每一帧动画中包含的信息(平移、旋转、缩放、透明度),在Transformation类中最重要的属性就是alpha和Matrix,这两个是真正存放一帧动画的所有信息的载体。方法中最重要的是:getTransformation()和applyTransformation(),第一个方法是由系统调用,根据动画当前时间计算出此时的Transformation信息,不必重写次方法,第二个方法是必须重写的,根据系统由第一个方法计算出的Transformation进行实际的动画实现。
首先来看Animation对象,当要设置动画的时候,一般都是先构建好Animation对象,然后使用下列形式的代码:
|
|
追踪View的startAnimation()方法,可以发现:
|
|
可以看到先是调用了View的setAnimation()方法给自己设置一个Animation对象,这个对象是View类中的一个名为mCurrentAnimation的成员变量,然后调用invalidate()来重绘自己。既然View给自己设置了Animation对象,就会有需要拿出来使用,在View.draw(Canvas, ViewGroup, long)方法中发现了它的调用:
|
|
在View中有两个draw方法,一个有一个参数,另一个有三个参数,分别是:
|
|
|
|
其中第一个参数的draw方法是具体的绘制过程(绘制背景、绘制内容、绘制前景……),含有三个参数的draw方法是由父布局调用的(ViewGroup.drawChild),因为android绘制的过程是从根布局开始的,子View是否能够绘制由父布局决定,这个方法就是提前处理绘制的地方,补间动画也是在此进行了处理以实现动画的效果。
从View三个参数的方法中可以看到首先是通过getAnimation()方法获取之前设置的animation对象,然后用它作为参数调用了applyLegacyAnimation()方法,接下里看看applyLegacyAnimation的实现:
|
|
其中主要调用了Animation的getTransformation()方法,这个三个参数的方法最后会调用重载它的两个参数的方法。
|
|
这个方法先将参数currentTime处理成一个float表示当前动画进度,然后将进度值传入插值器得到新的进度值,前者是均匀的,随着时间是一个直线的线性关系,而通过插值器计算后得到的是一个曲线的关系,然后将新的进度值和Transformation对象传入applyTransformation()方法中,接下来看看applyTransformation()方法的实现:
|
|
可以看到Animation的applyTransformation方法是空实现,具体实现它的是Animation的四个子类,而该方法正是真正的处理动画变化的过程。下面看一下四个子类的applyTransformation()方法的具体实现:
ScaleAnimation
12345678910111213141516171819protected void applyTransformation(float interpolatedTime, Transformation t) {float sx = 1.0f;float sy = 1.0f;float scale = getScaleFactor();if (mFromX != 1.0f || mToX != 1.0f) {sx = mFromX + ((mToX - mFromX) * interpolatedTime);}if (mFromY != 1.0f || mToY != 1.0f) {sy = mFromY + ((mToY - mFromY) * interpolatedTime);}if (mPivotX == 0 && mPivotY == 0) {t.getMatrix().setScale(sx, sy);} else {t.getMatrix().setScale(sx, sy, scale * mPivotX, scale * mPivotY);}}AlphaAnimation
12345protected void applyTransformation(float interpolatedTime, Transformation t) {final float alpha = mFromAlpha;t.setAlpha(alpha + ((mToAlpha - alpha) * interpolatedTime));}RotateAnimation
1234567891011protected void applyTransformation(float interpolatedTime, Transformation t) {float degrees = mFromDegrees + ((mToDegrees - mFromDegrees) * interpolatedTime);float scale = getScaleFactor();if (mPivotX == 0.0f && mPivotY == 0.0f) {t.getMatrix().setRotate(degrees);} else {t.getMatrix().setRotate(degrees, mPivotX * scale, mPivotY * scale);}}TranslateAnimation
123456789101112protected void applyTransformation(float interpolatedTime, Transformation t) {float dx = mFromXDelta;float dy = mFromYDelta;if (mFromXDelta != mToXDelta) {dx = mFromXDelta + ((mToXDelta - mFromXDelta) * interpolatedTime);}if (mFromYDelta != mToYDelta) {dy = mFromYDelta + ((mToYDelta - mFromYDelta) * interpolatedTime);}t.getMatrix().setTranslate(dx, dy);}
如此一来,动画的变化,就隐藏在Transformation之中了,既然已经知道了如何变化,就需要系统使用这种变化即应用到绘制中了。回到View的draw(Canvas canvas, ViewGroup parent, long drawingTime)方法,其中applyLegacyAnimation方法是用来获取变换的,其中有两个部分需要注意:
|
|
|
|
第一个是获取变换,第二个是获取绘制无效区域。
在View的draw方法中:
|
|
在这里分别从硬件加速和软件绘制上,对canvas进行矩阵变换。
可见applyTransformation()方法就是动画具体的实现,系统会以一个比较高的频率来调用这个方法,一般情况下是60FPS,是一个非常流畅的画面了,也就是16ms。而系统是怎样高频率调用这个方法的,就要得益于Choreographer机制了。下面来看Choreographer类,其是在ViewRootImpl的构造方法中被调用,代码如下:
|
|
再看到ViewRootImpl的scheduleTraversals()方法。
|
|
可以看到调用了mChoreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null),该方法辗转调用了两个内部方法,最终调用了postCallbackDelayedInternal()方法。
|
|
这个方法中,首先拿到当前的时间,然后将要执行的内容加入到一个mCallbackQueues中,再执行scheduleFrameLocked()方法或者发送一个Message。先看scheduleFrameLocked()方法:
|
|
if判断进去的部分是否使用垂直同步,暂时不考虑,else进去的部分还是把消息发送到mHandler对象中,接下来就看看mHandler对象,mHandler实例的类型是FrameHandler,它的定义就在Choreographer类中,源码实现如下:
|
|
它的处理方法中有三个分支,但最终都会调用这个doFrame()方法:
|
|
可以看到这个方法多次调用了doCallbacks()方法,从入参可以看出分别是CALLBACK_INPUT(输入),CALLBACK_ANIMATION(动画),CALLBACK_TRAVERSAL(遍历)等。接下来就看看doCallbacks()方法:
|
|
先看mCallbackQueues[]这个成员变量,它是一个CallbackQueue对象数组,它的下标是指类型,参数callbackType传给了mCallbackQueues[callbackType]中,而callbackType就是前面说的常量:CALLBACK_INPUT(输入),CALLBACK_ANIMATION(动画),CALLBACK_TRAVERSAL(遍历)。只需要根据不同的callbackType,就可以从这个数组里面取出不同类型的CallbackQueue对象来。而这个CallbackQueue又是Choreographer的一个内部类,其中有两个很重要的方法,分别是:extractDueCallbacksLocked(long)和addCallbackLocked(long, Object, Object)。
先来看addCallbackLocked(long, Object, Object)方法:
|
|
首先它通过一个内部方法构建了一个CallbackRecord对象,然后在后面将参数中的对象链接在CallbackRecord尾部,其实CallbackRecord就是一个链表结构的对象。
接着看extractDueCallbacksLocked(long)方法:
|
|
这个方法是根据当前的时间,选出执行链表中与该时间最近的一个操作来处理,实际上,可以通俗地理解为跳帧。
小结
到这里,可以得出以下结论:
- 控制外部输入事件处理,动画执行,UI变化都是在同一个类中做的处理,即是Choreographer,其中它规定了理想的运行间隔为10ms,因为各种操作需要花费一定的时间,所以外部执行的间隔统计出来是大约16ms。
- 在Choreographer对象中有三条链表,分别保存着待处理的输入事件,待处理的动画事件,代处理的遍历事件。
- 每次执行的时候,Choreographer会根据当前的时间,只处理事件链表中最后一个事件,当有耗时操作在主线程中时,事件不能及时执行,就会出现所谓的跳帧,卡顿现象。
- Choreographer的公有方法postCallback是往事件链表中放事件的方法,而doFrame是消耗这些事件的方法。