Android 动画分析之Tween动画分析

前言:

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


动画分类:

那android为我们提供了哪些动画呢:目前是三种:Tween动画,Frame动画,以及3.0后推出的Property动画即属性动画。

Tween动画:通过对场景的对象进行图形变化(包括缩放,平移,旋转,改变透明度)来产生动画效果。

Frame动画:顺序的播放事先做好的图像。类似放电影。

Property动画:即通过改变对象的实际属性来达到动画效果。

(这里为啥说实际属性呢,因为上述两种动画虽然对象在视觉上的确是移动了,但通过真实测试,并没有改变属性,这也是经常遇到的一个面试题目)


动画的代码分析:

我们在写布局定义控件一个道理,既可以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完成的,控件本身的位置并没有改变,这点需要与属性动画区分开。




你可能感兴趣的:(Android)