Android 动画分析之Tween动画分析

前言:
我们选择了android开发或ios开发,是不是见到不错的app,就想看看他们的一些动画效果,交互体验,以及一些有创意的功能。今天我们就来一起学习讲解android中的动画,以及动画原理,以便后续独立开发一些复杂的动画。


动画分类:
那android为我们提供了哪些动画呢:目前是三种:Tween动画,Frame动画,以及3.0后推出的Property动画即属性动画。
Tween动画:通过对场景的对象进行图形变化(包括缩放,平移,旋转,改变透明度)来产生动画效果。
Frame动画:顺序的播放事先做好的图像。类似放电影。
Property动画:即通过改变对象的实际属性来达到动画效果。
(这里为啥说实际属性呢,因为上述两种动画虽然对象在视觉上的确是移动了,但通过真实测试,并没有改变属性,这也是经常遇到的一个面试题目)


动画的代码分析:
【Android 动画分析之Tween动画分析】我们在写布局定义控件一个道理,既可以layout定义,也可以代码定义,没疑问吧,Tween动画与Frame动画也可以用XML和代码两种方式来完成,殊途同归都是为了构建Animation对象:(说到这,其实大家学习编程,也要类比)
Animation trans = new TranslateAnimation(...);
这时候最重要的一个对象Animation就出现了,我们先看看Animation对象:

/** * Abstraction for an Animation that can be applied to Views, Surfaces, or * other objects. See the {@link android.view.animation animation package * description file}. */ public abstract class Animation implements Cloneable

从源码可以看粗, Animation对象是一个抽象类。TranslateAnimation,ScaleAnimation等都继承自Animation。那我们在看看具体类 TranslateAnimation的代码
@Override 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); }@Override public void initialize(int width, int height, int parentWidth, int parentHeight) { super.initialize(width, height, parentWidth, parentHeight); mFromXDelta = resolveSize(mFromXType, mFromXValue, width, parentWidth); mToXDelta = resolveSize(mToXType, mToXValue, width, parentWidth); mFromYDelta = resolveSize(mFromYType, mFromYValue, height, parentHeight); mToYDelta = resolveSize(mToYType, mToYValue, height, parentHeight); }


我只拿了一部分代码,自己可以具体看看,该类除了构造函数外,有两个比较重要的方法: applyTransformation 以及 initialize方法。 这两个方法都是重写的方法。我们继续寻根求源,看看子类里的方法在什么时候调用的。经过搜索,applyTransformation实在Animation类的getTransfromation类中调用。

public boolean getTransformation(long currentTime, Transformation outTransformation) { if (mStartTime == -1) { mStartTime = currentTime; }final long startOffset = getStartOffset(); final long duration = mDuration; float normalizedTime; if (duration != 0) { normalizedTime = ((float) (currentTime - (mStartTime + startOffset))) / (float) duration; } else { // time is a step-change with a zero duration normalizedTime = currentTime < mStartTime ? 0.0f : 1.0f; }final boolean expired = normalizedTime >= 1.0f; mMore = !expired; if (!mFillEnabled) normalizedTime = Math.max(Math.min(normalizedTime, 1.0f), 0.0f); if ((normalizedTime >= 0.0f || mFillBefore) && (normalizedTime <= 1.0f || mFillAfter)) { if (!mStarted) { fireAnimationStart(); mStarted = true; if (USE_CLOSEGUARD) { guard.open("cancel or detach or getTransformation"); } }if (mFillEnabled) normalizedTime = Math.max(Math.min(normalizedTime, 1.0f), 0.0f); if (mCycleFlip) { normalizedTime = 1.0f - normalizedTime; }final float interpolatedTime = mInterpolator.getInterpolation(normalizedTime); applyTransformation(interpolatedTime, outTransformation); }

而getTransformation方法实际是由系统调用的,根据动画当前时间计算Transformation信息,而实现类里的applyTransformation则是根据getTransformation里计算出的信息进行实际的动画实现。那我们一直说的Transformation是啥呢(Animation类的两个比较重要的属性就是监听和这个Transformation,其中AnimationListener是监听动画的开始,结束等),我们继续回到animation源码里去查找,然后看看Transformation到底都有啥:
public class Transformation { /** * Indicates a transformation that has no effect (alpha = 1 and identity matrix.) */ public static final int TYPE_IDENTITY = 0x0; /** * Indicates a transformation that applies an alpha only (uses an identity matrix.) */ public static final int TYPE_ALPHA = 0x1; /** * Indicates a transformation that applies a matrix only (alpha = 1.) */ public static final int TYPE_MATRIX = 0x2; /** * Indicates a transformation that applies an alpha and a matrix. */ public static final int TYPE_BOTH = TYPE_ALPHA | TYPE_MATRIX; protected Matrix mMatrix; protected float mAlpha; protected int mTransformationType; private boolean mHasClipRect; private Rect mClipRect = new Rect();


这里包含了Matrix,mAlpha等,mAlpha是存放透明度度信息的,而Matrix,则存放了平移,旋转等信息,这二者构成了Transformation信息的载体。
是否注意到了上边applyTransformation里有个matrix的调用 t.getMatrix()。 这个Matrix到底是什么,但从字母看是矩阵,我们线性代数,数据结构都经常接触到矩阵,动画里的矩阵干嘛用的,我们看回Matrix类

public class Matrix {public static final int MSCALE_X = 0; //!< use with getValues/setValues public static final int MSKEW_X= 1; //!< use with getValues/setValues public static final int MTRANS_X = 2; //!< use with getValues/setValues public static final int MSKEW_Y= 3; //!< use with getValues/setValues public static final int MSCALE_Y = 4; //!< use with getValues/setValues public static final int MTRANS_Y = 5; //!< use with getValues/setValues public static final int MPERSP_0 = 6; //!< use with getValues/setValues public static final int MPERSP_1 = 7; //!< use with getValues/setValues public static final int MPERSP_2 = 8; //!< use with getValues/setValues/** @hide */ public final static Matrix IDENTITY_MATRIX = new Matrix() { void oops() { throw new IllegalStateException("Matrix can not be modified"); }@Override public void set(Matrix src) { oops(); }@Override public void reset() { oops(); }@Override public void setTranslate(float dx, float dy) { oops(); }@Override public void setScale(float sx, float sy, float px, float py) { oops(); }@Override public void setScale(float sx, float sy) { oops(); }@Override public void setRotate(float degrees, float px, float py) { oops(); }@Override public void setRotate(float degrees) { oops(); }@Override public void setSinCos(float sinValue, float cosValue, float px, float py) { oops(); }@Override public void setSinCos(float sinValue, float cosValue) { oops(); }@Override public void setSkew(float kx, float ky, float px, float py) { oops(); }@Override public void setSkew(float kx, float ky) { oops(); }

@Override public boolean setConcat(Matrix a, Matrix b) { oops(); return false; }@Override public boolean preTranslate(float dx, float dy) { oops(); return false; }@Override public boolean preScale(float sx, float sy, float px, float py) { oops(); return false; }@Override public boolean preScale(float sx, float sy) { oops(); return false; }@Override public boolean preRotate(float degrees, float px, float py) { oops(); return false; }@Override public boolean preRotate(float degrees) { oops(); return false; }@Override public boolean preSkew(float kx, float ky, float px, float py) { oops(); return false; }@Override public boolean preSkew(float kx, float ky) { oops(); return false; }@Override public boolean preConcat(Matrix other) { oops(); return false; }@Override public boolean postTranslate(float dx, float dy) { oops(); return false; }@Override public boolean postScale(float sx, float sy, float px, float py) { oops(); return false; }@Override public boolean postScale(float sx, float sy) { oops(); return false; }@Override public boolean postRotate(float degrees, float px, float py) { oops(); return false; }@Override public boolean postRotate(float degrees) { oops(); return false; }

我们看到上述源码,setXXX,PreXXX,postXXX,是对translate,scale,rotate的设置。也就是getMatrix()是获取得到当前帧的动画中在矩阵中的信息。 其实android 中的 Matrix是个3*3的矩阵,包含的是平移,旋转,缩放的区域,进而实现对平移,旋转,缩放的动画实现。
Matrix的具体使用,放在后续讲解,今天只从源代码分析。如果同学急于学习,请先看一篇讲解吧:
Matrix

动画的使用:
我们在执行动画时,经常是view.startAnimation.看下源码:

/** * Start the specified animation now. * * @param animation the animation to start now */ public void startAnimation(Animation animation) { animation.setStartTime(Animation.START_ON_FIRST_FRAME); setAnimation(animation); invalidateParentCaches(); invalidate(true); }


这个是view的启动执行动画的方法,setAnimation设置动画对象,并赋给成员变量mCurrentAnimation,然后invalidate() 重绘自身。 重绘视图的时候调用到drawChild,这时候获取与View绑定的Animation,在设置的动画时间不结束,就会变换矩阵Matrix,绘制完成,在获取新的一帧的换换矩阵,知道动画结束。在绘制子视图时drawChild(),会调用方法:

/** * This method is called by ViewGroup.drawChild() to have each child view draw itself. * * This is where the View specializes rendering behavior based on layer type, * and hardware acceleration. */ boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) { final boolean hardwareAcceleratedCanvas = canvas.isHardwareAccelerated(); /* If an attached view draws to a HW canvas, it may use its RenderNode + DisplayList. * * If a view is dettached, its DisplayList shouldn't exist. If the canvas isn't * HW accelerated, it can't handle drawing RenderNodes. */ boolean drawingWithRenderNode = mAttachInfo != null && mAttachInfo.mHardwareAccelerated && hardwareAcceleratedCanvas; boolean more = false; final boolean childHasIdentityMatrix = hasIdentityMatrix(); final int parentFlags = parent.mGroupFlags; if ((parentFlags & ViewGroup.FLAG_CLEAR_TRANSFORMATION) != 0) { parent.getChildTransformation().clear(); parent.mGroupFlags &= ~ViewGroup.FLAG_CLEAR_TRANSFORMATION; }Transformation transformToApply = null; boolean concatMatrix = false; final boolean scalingRequired = mAttachInfo != null && mAttachInfo.mScalingRequired; final Animation a = getAnimation(); if (a != null) { more = applyLegacyAnimation(parent, drawingTime, a, scalingRequired); concatMatrix = a.willChangeTransformationMatrix(); if (concatMatrix) { mPrivateFlags3 |= PFLAG3_VIEW_IS_ANIMATING_TRANSFORM; } transformToApply = parent.getChildTransformation(); } else { if ((mPrivateFlags3 & PFLAG3_VIEW_IS_ANIMATING_TRANSFORM) != 0) { // No longer animating: clear out old animation matrix mRenderNode.setAnimationMatrix(null); mPrivateFlags3 &= ~PFLAG3_VIEW_IS_ANIMATING_TRANSFORM; } if (!drawingWithRenderNode

此方法里有调用了View.applyLegacyAnimation()方法,我们继续,可以看到源码里调用了Animation类的getTransformation方法,进而一步一步又回到上述分析的applyTransformation方法。这样,这个调用过程就关联起来了。

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) { 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); onAnimationStart(); }final Transformation t = parent.getChildTransformation(); boolean more = a.getTransformation(drawingTime, t, 1f); if (scalingRequired && mAttachInfo.mApplicationScale != 1f) { if (parent.mInvalidationTransformation == null) { parent.mInvalidationTransformation = new Transformation(); } invalidationTransform = parent.mInvalidationTransformation; a.getTransformation(drawingTime, invalidationTransform, 1f); } else { invalidationTransform = t;


通过上述分析,Tween动画的流程也就走通了。大体总结时: 1.view.startAnimation 启动动画
2.invalidate() 重绘
3.执行drawChlild,进而调用draw方法获取animation对象
4.View.applyLegacyAnimation方法里调用Animation类的getTransformation方法
5.在具体类如Scaleanimation类实现Animation类的applyTransformation方法
6.在applyTransformation方法里通过矩阵(Matrix)变换实现动画
从而动画原理就是这个过程,对于矩阵的实现,后续会有文章展现。需要注意的是还有AnimationUtils(辅助类),AnimationSet(继承Animation),大家可以去分析分析。
最后需要注意的是:执行动画时并不是控件本身在改变,而是它的父view完成的,控件本身的位置并没有改变,这点需要与属性动画区分开。






    推荐阅读