Android动画原理
一、前言
Android动画包含三种:补间动画(Tween Animation),帧动画(Frame Animation),属性动画 (Property Animation)。其中属性动画是从Android 3.0之后加入的。
本文着重介绍三种动画的实现原理,阅读本文的前提是应该可以简单的使用上述三种动画。
如不熟悉可以参考以下链接,
二、补间动画原理(Tween Animation)
原理:在绘制的过程中,尝试获取动画在当前时刻的变换,然后应用到view的绘制中。
说明:
绘制是显示view所必不可少的过程,通过view的draw方法可以看到绘制的流程。
查看View.java的源码,可以发现有两个draw方法,一个有一个参数,一个有三个参数。分别是
public void draw(Canvas canvas)
boolean draw(Canvas canvas, ViewGroup parent, long drawingTime)
其中一个参数的draw方法是具体的绘制过程(绘制背景、绘制内容、绘制前景...)
含有三个参数的draw方法是由父布局调用的(ViewGroup.drawChild),因为Android绘制的过程是从根布局开始,
子View是否能够绘制由父布局决定。这个方法就是提前处理绘制的位置的地方,补间动画也是在此处进行了处理以实现动画的效果。
先举一个例子,
Animation translateAnimation = new TranslateAnimation(0, 100, 0, 0);
translateAnimation.setDuration(500);
translateAnimation.setInterpolator(new AccelerateInterpolator());
translateAnimation.setFillAfter(true);//设置动画结束后保持当前的位置(即不返回到动画开始前的位置)
imageView.startAnimation(translateAnimation);
一个动画重要的东西就四个部分,开始时间,结束时间,做什么,如何做。
上述是一个imageView在x方向移动的动画,可以看到它符合上面所说的四个部分,
做什么:沿x方向移动100
如何做:直线且不断加速
开始时间:startAnimtion调用的那一刻
结束时间: 开始时间+500
从上面也大致了解到补间动画的框架是通过先建立一个Animation对象并对其设置一些属性,然后将它与一个view建立关联,
最后这个view可以执行这个动画。
由此我们开始分析Animation的源码(基于Android 6.0):
Animation及相关类的源码在frameworks/base/core/java/android/view/animation包中。
其中Animation类是一个抽象类,虽然它没有抽象方法,但是它有一个空方法
protected void applyTransformation(float interpolatedTime, Transformation t)
可以看到,它有两个参数,第一个参数是 interpolatedTime 它代表插值后的时间,第二个参数是Transformation类的实例
Transformation是一个实体类,它主要的内容是透明度和一个矩阵。
所以子类实现了applyTransformation方法后可以针对插值时间来对Transformation做一定的操作来实现变化。
同时可以看到applyTransformation是在Animtaion的getTransformation中调用的,
public boolean getTransformation(long currentTime, Transformation outTransformation)
其中currentTime作为一个参数,虽然期望的是当前时间,但是不是用还是由调用者决定。
第二个参数目的是调用者传进来作为收集变化信息
此方法除了调用回调(开始,结束,重复),重点是调用applyTransformation前的这一句
final float interpolatedTime = mInterpolator.getInterpolation(normalizedTime);
插值器的目的是为了将控制动画速度的过程抽离出来,它是通过改变时间来改变最终效果。
normalizedTime 的范围是0.0f~1.0f
但是interpolatedTime可以超出这个范围
所以在实现applyTransformation的时候需要考虑插值后的时间这个范围之外的情况。
下面以TranslateAnimation和AccelerateInterpolator来说明是如何实现的
可以知道
TranslateAnimation主要实现applyTransformation方法
AccelerateInterpolator主要实现getInterpolation方法
下面是两个方法的源码:
protected 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的矩阵偏移
}
public float getInterpolation(float input) {
if (mFactor == 1.0f) {
return input * input; //输入0.5 返回0.25
} else {
return (float)Math.pow(input, mDoubleFactor);
}
}
如此一来,动画的变化,就隐藏在Transformation之中了。
既然已经知道了如何变化,现在就需要系统使用这种变化即应用到绘制中了。
移步到View.java之中的draw(--,--,--)方法,
其中applyLegacyAnimation方法是用来获取变换的,
其中有两个部分需要注意
a.getTransformation(drawingTime, invalidationTransform, 1f);
a.getInvalidateRegion(0, 0, mRight - mLeft, mBottom - mTop, region,
invalidationTransform);
第一个就是获取变换,第二个是获取绘制无效区域。
其实这个无效区域是绘制后的无效区域,因为很有可能需要下次绘制(保证动画连续性)。
而view在draw之中
if (transformToApply != null) {
if (concatMatrix) {
if (drawingWithRenderNode) {
renderNode.setAnimationMatrix(transformToApply.getMatrix());
} else {
// Undo the scroll translation, apply the transformation matrix,
// then redo the scroll translate to get the correct result.
canvas.translate(-transX, -transY);
canvas.concat(transformToApply.getMatrix());
canvas.translate(transX, transY);
}
parent.mGroupFlags |= ViewGroup.FLAG_CLEAR_TRANSFORMATION;
}
float transformAlpha = transformToApply.getAlpha();
if (transformAlpha < 1) {
alpha *= transformAlpha;
parent.mGroupFlags |= ViewGroup.FLAG_CLEAR_TRANSFORMATION;
}
}
分别从硬件加速和软件绘制上 对canvas进行矩阵变换.
除了TranslateAnimation,系统还有AlphaAnimation和ClipRectAnimation可供选择.
三、逐帧动画原理(Frame Animation)
原理:使用了Choreographer机制
AnimationDrawable类是一个实现了逐帧动画的类,可以看出,它只用来进行图片的动态切换.
AnimationDrawable类源码在frameworks/base/graphics/java/android/graphics/drawable/中
public class AnimationDrawable extends DrawableContainer implements Runnable, Animatable
首先看到AnimationDrawable继承了DrawableContainer,因为DrawableContainer是一个drawable的容器,可以保存多个图片
同时,实现了Runnable接口,重写了run方法
根据源码中的start方法,它调用了setFrame方法,方法内部最重要的调用就是调用了
scheduleSelf(this, SystemClock.uptimeMillis() + mAnimationState.mDurations[frame]);
上述方法实现是在Drawable类实现的
public void scheduleSelf(Runnable what, long when) {
final Callback callback = getCallback();
if (callback != null) {
callback.scheduleDrawable(this, what, when);
}
}
其中callback一般是drawable相关联的view.
可以看出,它接着回调了view的scheduleDrawable方法
而这个方法最终会
mAttachInfo.mViewRootImpl.mChoreographer.postCallbackDelayed(
Choreographer.CALLBACK_ANIMATION, what, who,
Choreographer.subtractFrameDelay(delay));
来实现delay后,可以绘制下一帧的效果.
因为AnimationDrawable所实现了runnable接口的run方法就是执行nextFrame.
同时,AnimatedStateListDrawable和AnimatedVectorDrawable和AnimatedRotateDrawable(隐藏)
都是具备一定的动画效果
其中,AnimatedStateListDrawable是在view状态切换时可以实现两个状态直接的渐变
如果想了解更多,需要对drawable有所了解.
四、属性动画原理(Property Animation)
原理:使用了Choreographer机制
简单的说,Chreographer是组织上层进行处理绘制的控制类,它会在每次vsync信号来临时,执行与绘制相关的过程.
属性动画相关的方法在源码中所在的位置是frameworks/base/core/java/android/view/animation
属性动画的基类是Animator.
与Animation(补间动画)不同,Animator的确定过程发生在绘制之前(甚至是布局之前).
因为ValueAnimator引入了Choreographer,Choreographer是Vsync信号到来后进行view更新的控制类。
它通过mChoreographer.postCallback(Choreographer.CALLBACK_ANIMATION, mAnimate, null);
这个动作来实现下次绘制之前,可以执行mAnimate任务来设置view的一些属性来实现动画。
上述所说的view的一些属性包括X,translateX,TranslateZ,ScaleX......等属性
对这些属性的控制都会放到一个矩阵(Matrix),而这个矩阵放在RenderNode中
需要注意的是,虽然RenderNode主要为硬件渲染服务,但是它保存了一些属性是软件渲染也需要的,比如上面说的矩阵.
通过硬件加速绘制时,因为矩阵已经在RenderNode中了,所以在draw方法中不用做特殊处理.
而在软件渲染中(draw方法),
if (!childHasIdentityMatrix && !drawingWithRenderNode) {
canvas.translate(-transX, -transY);
canvas.concat(getMatrix());
canvas.translate(transX, transY);
}
其中,childHasIdentityMatrix 代表是不是单位矩阵
drawingWithRenderNode 代表是不是开启了硬件加速
说明:
观察ValueAnimator的源码
发现方法
private void scheduleAnimation() {
if (!mAnimationScheduled) {
mChoreographer.postCallback(Choreographer.CALLBACK_ANIMATION, mAnimate, null);
mAnimationScheduled = true;
}
}
以及mAnimate为
// Called by the Choreographer.
final Runnable mAnimate = new Runnable() {
@Override
public void run() {
mAnimationScheduled = false;
doAnimationFrame(mChoreographer.getFrameTime());
}
};
你如果对ValueAnimator添加了更新监听(addUpdateListener)
那么你可以在每次更新的回调(发生在上面的doAnimationFrame里[实际是animateValue])
比如进行view.setTranslateX(10),这种处理在随后的绘制中就会有所体现
参考文档: