Android补间动画源码阅读
前言
Android补间动画是一项相对古老的动画框架,补间动画通过在Canvas上做绘制操作实现动画效果,相对于后来的属性动画效率要高,如果有些动画只是展示效果,那么补间动画是一种不错的选择。系统内置了alpha、translate、scale和rotate四种动画效果,现在通过这四种简单的动画来看一下补间动画的实现原理。
内置效果
补间动画可以使用xml来定义也可以使用源代码的方式定义,两者是等价的,这里采用XML形式来定义这些简单动画效果。
<?xml version="1.0" encoding="utf-8"?>
<rotate xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="500"
android:fillAfter="true"
android:fromDegrees="0"
android:toDegrees="360"
android:pivotX="50%"
android:pivotY="50%">
</rotate>
然后使用AnimationUtils.loadAnimation方法加载动画XML实现,View需要指定动画只需要调用View.startAnimation就可以了。
rotateAnimation = (RotateAnimation)
AnimationUtils.loadAnimation(this, R.anim.rotate);
button.startAnimation(rotateAnimation);
这里只是简单的实现了旋转动画效果,就从这个简单的示例开始分析实现的代码。
源码分析
查看View.startAnimation的源码实现,最开始设置了旋转动画的开始时间,然后清空父布局的缓存并且将当前View置为无效状态,invalidate会导致整个界面的重新绘制操作。
public void startAnimation(Animation animation) {
animation.setStartTime(Animation.START_ON_FIRST_FRAME);
setAnimation(animation);
invalidateParentCaches();
invalidate(true);
}
接下来查看View的父布局ViewGroup的dispatchDraw方法,在该方法内部又会回调各个子控件的draw方法实现各个控件元素的绘制操作。
// 遍历布局内部的所有子控件
for (int i = 0; i < childrenCount; i++) {
....
final int childIndex = getAndVerifyPreorderedIndex(childrenCount, i, customOrder);
final View child = getAndVerifyPreorderedView(preorderedList, children, childIndex);
// 如果child包含animation动画而且是可见的那么调用drawChild方法
if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {
more |= drawChild(canvas, child, drawingTime);
}
}
dispatchDraw内部会在遍历所有的子控件时判断子控件是否有动画效果,如果有就会调用drawChild方法来绘制子控件,在drawChild方法里调用了super.draw方法,这个方法内部会判断当前View是否有动画效果执行。
boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) {
...
Transformation transformToApply = null;
boolean concatMatrix = false;
final boolean scalingRequired = mAttachInfo != null && mAttachInfo.mScalingRequired;
// 判断当前的View是否包含动画效果
final Animation a = getAnimation();
if (a != null) {
// 如果有动画效果就执行applyLegacyAnimation
more = applyLegacyAnimation(parent, drawingTime, a, scalingRequired);
concatMatrix = a.willChangeTransformationMatrix();
if (concatMatrix) {
mPrivateFlags3 |= PFLAG3_VIEW_IS_ANIMATING_TRANSFORM;
}
transformToApply = parent.getChildTransformation();
}
...
}
注意这个返回结果如果为true代表当前的动画还没有执行完成,后面还有再invalidate继续重新执行重新绘制操作。
private boolean applyLegacyAnimation(ViewGroup parent, long drawingTime,
Animation a, boolean scalingRequired) {
Transformation invalidationTransform;
final int flags = parent.mGroupFlags;
final boolean initialized = a.isInitialized();
// 判断当前的补间动画是否已经被初始化了
if (!initialized) {
// 没有被初始化那么就传入当前view的宽度、高度和父控件的宽度和高度
a.initialize(mRight - mLeft, mBottom - mTop, parent.getWidth(), parent.getHeight());
a.initializeInvalidateRegion(0, 0, mRight - mLeft, mBottom - mTop);
if (mAttachInfo != null) a.setListenerHandler(mAttachInfo.mHandler);
// 执行设置到Animation里的动画生命周期监听事件
onAnimationStart();
}
// 新建一个Transformation对象
final Transformation t = parent.getChildTransformation();
// 通过动画对象获取transformation对象数据,同时返回是否已经执行完成
boolean more =
a.getTransformation(drawingTime, t, 1f);
...
return more;
}
Transformation这个类又代表什么数据呢,为什么这里需要从Animation中获取这个对象的数据值呢?现在先看Transformation的代码实现。
public class Transformation {
// 不做任何变换
public static final int TYPE_IDENTITY = 0x0;
// 改变View的alpha属性
public static final int TYPE_ALPHA = 0x1;
// 改变Matrix属性
public static final int TYPE_MATRIX = 0x2;
// 即改变alpha又改变Matrix
public static final int TYPE_BOTH = TYPE_ALPHA | TYPE_MATRIX;
// 保存Matrix和alpha当前值
protected Matrix mMatrix;
protected float mAlpha;
protected int mTransformationType;
private boolean mHasClipRect;
private Rect mClipRect = new Rect();
public Transformation() {
clear();
}
// 清除已有值
public void clear() {
if (mMatrix == null) {
mMatrix = new Matrix();
} else {
mMatrix.reset();
}
mClipRect.setEmpty();
mHasClipRect = false;
mAlpha = 1.0f;
mTransformationType = TYPE_BOTH;
}
// 获取变换的方式
public int getTransformationType() {
return mTransformationType;
}
public void setTransformationType(int transformationType) {
mTransformationType = transformationType;
}
// 克隆一个Transform对象
public void set(Transformation t) {
mAlpha = t.getAlpha();
mMatrix.set(t.getMatrix());
if (t.mHasClipRect) {
setClipRect(t.getClipRect());
} else {
mHasClipRect = false;
mClipRect.setEmpty();
}
mTransformationType = t.getTransformationType();
}
// 组合一个Transform对象
public void compose(Transformation t) {
mAlpha *= t.getAlpha();
mMatrix.preConcat(t.getMatrix());
if (t.mHasClipRect) {
Rect bounds = t.getClipRect();
if (mHasClipRect) {
setClipRect(mClipRect.left + bounds.left, mClipRect.top + bounds.top,
mClipRect.right + bounds.right, mClipRect.bottom + bounds.bottom);
} else {
setClipRect(bounds);
}
}
}
public void postCompose(Transformation t) {
mAlpha *= t.getAlpha();
mMatrix.postConcat(t.getMatrix());
if (t.mHasClipRect) {
Rect bounds = t.getClipRect();
if (mHasClipRect) {
setClipRect(mClipRect.left + bounds.left, mClipRect.top + bounds.top,
mClipRect.right + bounds.right, mClipRect.bottom + bounds.bottom);
} else {
setClipRect(bounds);
}
}
}
// 各种getter/setter这里就省略了
...
}
如果对图形图像处理有稍微了解都知道3*3的Matrix可以操作各种图形变换,这里有一个公式可以表征矩阵中的每一个元素所能够支持的变换。也就是说Matrix封装了tranlate、scale和rotate三种内置的补间动画,mAlpha这个值就封装了alpha动画值,所以这个Transform对象其实时封装了所有Matrix支持的图形变换和Alpha动画数据,之所以没有说Matrix封装了translate、scale和roate三种动画是因为Matrix还可以支持其他的图形变换操作。
接下来查看RotateAnimation的实现代码逻辑,前面定义的rotate.xml里定义了旋转轴的位置,这里根据旋转轴的百分比和applyLegacyAnimation传入的当前view宽高和父控件宽高计算旋转轴位置。
@Override
public void initialize(int width, int height, int parentWidth, int parentHeight) {
super.initialize(width, height, parentWidth, parentHeight);
mPivotX = resolveSize(mPivotXType, mPivotXValue, width, parentWidth);
mPivotY = resolveSize(mPivotYType, mPivotYValue, height, parentHeight);
}
applyLegacyAnimation方法里调用了getTranformation获取封装动画效果的Transformation对象数据。
public boolean getTransformation(long currentTime, Transformation outTransformation) {
// 规范化当前执行时间
...
// 根据当前时间获取目前动画执行的数据
final float interpolatedTime = mInterpolator.getInterpolation(normalizedTime);
applyTransformation(interpolatedTime, outTransformation);
}
getTransformation方法根据执行开始的流逝时间通过applyTransformation获取当前动画执行到的数据。查看RotateAnimation的applyTransformation方法发现它在内部正设置Matrix的rotate数值。
@Override
protected 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);
}
}
再回到前面的draw方法,查看后续的实现代码。
boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) {
...
if (transformToApply != null) {
if (concatMatrix) {
// 执行matrix里设置的动画效果
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;
}
}
if (!childHasIdentityMatrix && !drawingWithRenderNode) {
canvas.translate(-transX, -transY);
canvas.concat(getMatrix());
canvas.translate(transX, transY);
}
...
// 处理alpha修改导致的界面透明度变化
if (alpha < 1 || (mPrivateFlags3 & PFLAG3_VIEW_IS_ANIMATING_ALPHA) != 0) {
...
if (!onSetAlpha(multipliedAlpha)) {
if (drawingWithRenderNode) {
renderNode.setAlpha(alpha * getAlpha() * getTransitionAlpha());
} else if (layerType == LAYER_TYPE_NONE) {
canvas.saveLayerAlpha(sx, sy, sx + getWidth(), sy + getHeight(),
multipliedAlpha);
}
} else {
// Alpha is handled by the child directly, clobber the layer's alpha
mPrivateFlags |= PFLAG_ALPHA_SET;
}
}
}
可以看到Matrix里包含的动画数据和alpha数据都会被Canvas对象应用,之后Canvas绘制View的内容时就会导致内容出现动画效果。
总结
Transformation对象封装了补间动画的动画执行数据,每次View重绘的时候都会从Animation里重新获取当前的变换数据,之后Matrix和Alpha的变化数据都会被Canvas重用,再绘制View内容时产生动画效果。如果能够重写Animation的applytransmation使得每次获取的动画数据不同,那么就可以实现自定义的补间动画效果。
下一篇: Straight Shot