Android动画完全解析--View动画

一、View动画

Android动画完全解析--View动画_第1张图片

1、常见的4中View动画:AlphaAnimation、ScaleAnimation、TranslateAnimation、RotateAnimation

使用动画的方式有两种:一种是xml形式、另一种是java代码。使用起来都比较简单。还有一种AnimationSet,它是动画集合,将几种动画合在一起使用,下面AnimationSet来写动画。

创建缩放/透明动画


    //创建AnimationSet对象
    aSet = new AnimationSet(false);
    //创建动画对象
    sAnim = new ScaleAnimation(1, 0.5f, 1, 0.5f);
    //设置动画执行时间
    sAnim.setDuration(3000);
    //添加动画到集合中
    aSet.addAnimation(sAnim);
    aAnim = new AlphaAnimation(1, 0.5f);
    aAnim.setDuration(3000);
    aSet.addAnimation(aAnim);

开始执行动画


    aSet.start() ;
    btn_sys.setAnimation(aSet) ;

就这样,完成了一个动画集合的小例子,其它几种动画的使用方法类似。

2.View动画的源码分析

分析源码之前,我们需要知道这4种动画其实都是Animation的子类,而如果想要实现动画效果,则必须重写applyTransformation()方法,这点可以从这个方法的注释可以看出。


     /**
     * Helper for getTransformation. Subclasses should implement this to apply
     * their transforms given an interpolation value.  Implementations of this
     * method should always replace the specified Transformation or document
     * they are doing otherwise.
     * 
     * @param interpolatedTime The value of the normalized time (0.0 to 1.0)
     *        after it has been run through the interpolation function.
     * @param t The Transformation object to fill in with the current
     *        transforms.
     */
    protected void applyTransformation(float interpolatedTime, Transformation t) {
    }

OK,这里我们分析ScaleAnimation,其它三个可以自行分析。

ScaleAnimation

首先,我们看下构造函数


    public ScaleAnimation(float fromX, float toX, float fromY, float toY,
            float pivotX, float pivotY) {
        mResources = null;
        mFromX = fromX;
        mToX = toX;
        mFromY = fromY;
        mToY = toY;

        mPivotXType = ABSOLUTE;
        mPivotYType = ABSOLUTE;
        mPivotXValue = pivotX;
        mPivotYValue = pivotY;
        initializePivotPoint();
    }

源码很简单,只是将传递过来的变量赋值,然后调用initializePivotPoint()方法


    private void initializePivotPoint() {
        if (mPivotXType == ABSOLUTE) {
            mPivotX = mPivotXValue;
        }
        if (mPivotYType == ABSOLUTE) {
            mPivotY = mPivotYValue;
        }
    }

在构造函数执行完之后,马上就会执行initialize(),获取缩放中心点坐标


    @Override
    public void initialize(int width, int height, int parentWidth, int parentHeight) {
        super.initialize(width, height, parentWidth, parentHeight);

        mFromX = resolveScale(mFromX, mFromXType, mFromXData, width, parentWidth);
        mToX = resolveScale(mToX, mToXType, mToXData, width, parentWidth);
        mFromY = resolveScale(mFromY, mFromYType, mFromYData, height, parentHeight);
        mToY = resolveScale(mToY, mToYType, mToYData, height, parentHeight);

        mPivotX = resolveSize(mPivotXType, mPivotXValue, width, parentWidth);
        mPivotY = resolveSize(mPivotYType, mPivotYValue, height, parentHeight);
    }

紧接着,当调用animation.start()方法,


    public void setStartTime(long startTimeMillis) {
        mStartTime = startTimeMillis;
        mStarted = mEnded = false;
        mCycleFlip = false;
        mRepeated = 0;
        mMore = true;
    }

    /**
     * Convenience method to start the animation the first time
     * {@link #getTransformation(long, Transformation)} is invoked.
     */
    public void start() {
        setStartTime(-1);
    }

这个只是开启设置下时间,重要的是view.setAnimation()这个方法。如果上面的例子代码不书写view.setAnimation(),则不会出现动画效果,而如果书写.start()方法则依然可以执行,只不过再第二次执行时候的是在第一次执行的基础上,不是我们想要的结果。看源码


    /**
     * Sets the next animation to play for this view.
     * If you want the animation to play immediately, use
     * {@link #startAnimation(android.view.animation.Animation)} instead.
     * This method provides allows fine-grained
     * control over the start time and invalidation, but you
     * must make sure that 1) the animation has a start time set, and
     * 2) the view's parent (which controls animations on its children)
     * will be invalidated when the animation is supposed to
     * start.
     *
     * @param animation The next animation, or null.
     */
    public void setAnimation(Animation animation) {
        mCurrentAnimation = animation;

        if (animation != null) {
            // If the screen is off assume the animation start time is now instead of
            // the next frame we draw. Keeping the START_ON_FIRST_FRAME start time
            // would cause the animation to start when the screen turns back on
            if (mAttachInfo != null && !mAttachInfo.mScreenOn &&
                    animation.getStartTime() == Animation.START_ON_FIRST_FRAME) {
                animation.setStartTime(AnimationUtils.currentAnimationTimeMillis());
            }
            animation.reset();
        }
    }

从注释中可以看出,和start方法调用的是同一个方法。接着执行的是applyTransformation()这个方法,此方法是自行实现的,而且冲该方法的注释可以看出这个方法是是Helper for getTransformation.查看getTransformation()


    /**
     * Gets the transformation to apply at a specified point in time. Implementations of this
     * method should always replace the specified Transformation or document they are doing
     * otherwise.
     *
     * @param currentTime Where we are in the animation. This is wall clock time.
     * @param outTransformation A transformation object that is provided by the
     *        caller and will be filled in by the animation.
     * @return True if the animation is still running
     */
    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);
        }

        if (expired) {
            if (mRepeatCount == mRepeated) {
                if (!mEnded) {
                    mEnded = true;
                    guard.close();
                    fireAnimationEnd();
                }
            } else {
                if (mRepeatCount > 0) {
                    mRepeated++;
                }

                if (mRepeatMode == REVERSE) {
                    mCycleFlip = !mCycleFlip;
                }

                mStartTime = -1;
                mMore = true;

                fireAnimationRepeat();
            }
        }

        if (!mMore && mOneMoreTime) {
            mOneMoreTime = false;
            return true;
        }

        return mMore;
    }

这个方法中有一个特别重要的代码: applyTransformation(interpolatedTime, outTransformation);故它会调用applyTransformation方法来通过矩阵实现变换。

上面整体分析了Animation的执行流程,现在就具体来分析下ScaleAnimation是怎么做到缩放的。其实整个缩放动画一共就不到300行代码,而真正起决定作用的又只有几十行代码。


    @Override
    protected 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);
        }
    }

由源码可知最后动画还是通过矩阵变换来实现的。这里的interpolatedTime表示差值器,这个概念后面会提到,首先,获取缩放比例,然后,再根据不同时间段获取不同的sx值,最后通过矩阵变换来设置。 其实这个方法是会和前面提到过的getTransformation()这个方法一起执行起作用的,两个方法一直执行都动画结束。下面将会写一个Demo来演示这点。

3.自定义一个动画

前面使用AnimationSet将ScaleAnimation和AlphaAnimation结合起来,那么我们可不可以自定义一个Animation来实现这个效果呢?答案是肯定的。

a.首先,继承Animation


    public class ScaleAndAlphaAnimation extends Animation

b.接着就是利用构造函数将需要的参数传递进来


    public ScaleAndAlphaAnimation(float fromX, float toX, float fromY, float toY,float fromAlpha, float toAlpha){
        this.mFromX = fromX ;
        this.mFromY = fromY ;
        this.mToX = toX; 
        this.mToY = toY ;
        this.mFromAlpha = fromAlpha ;
        this.mToAlpha = toAlpha ;
        System.out.println("ScaleAndAlphaAnimation.ScaleAndAlphaAnimation()");
    }

c.最后就是复写applyTransformation方法了,这里我是参照ScaleAnimation和AlphaAnimation源码来写的


    @SuppressLint("NewApi") @Override
    protected 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);
        }
        t.getMatrix().setScale(sx, sy);
        //透明度动画设置
        final float alpha = mFromAlpha;
        t.setAlpha(alpha + ((mToAlpha - alpha) * interpolatedTime));
        System.out.println("ScaleAndAlphaAnimation.applyTransformation()");
        //        这在scaleanimation源码中代表缩放中心掉的位置
        //      if (mPivotX == 0 && mPivotY == 0) {
        //            t.getMatrix().setScale(sx, sy);
        //         } 
        //        else {
        //            t.getMatrix().setScale(sx, sy, scale * mPivotX, scale * mPivotY);
        //         }
                }

全部代码



    public class ScaleAndAlphaAnimation extends Animation{

        private float mFromX ;
        private float mFromY ;
        private float mToX ;
        private float mToY ;
        private float mFromAlpha ;
        private float mToAlpha ;
        //缩放比例
        private float scale = 1 ;

        public ScaleAndAlphaAnimation(float fromX, float toX, float fromY, float toY,float fromAlpha, float toAlpha){
            this.mFromX = fromX ;
            this.mFromY = fromY ;
            this.mToX = toX; 
            this.mToY = toY ;
            this.mFromAlpha = fromAlpha ;
            this.mToAlpha = toAlpha ;
            System.out.println("ScaleAndAlphaAnimation.ScaleAndAlphaAnimation()");
        }

        @SuppressLint("NewApi") @Override
        protected 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);
            }
            t.getMatrix().setScale(sx, sy);
            //透明度动画设置
            final float alpha = mFromAlpha;
            t.setAlpha(alpha + ((mToAlpha - alpha) * interpolatedTime));
            System.out.println("ScaleAndAlphaAnimation.applyTransformation()");
    //        这在scaleanimation源码中代表缩放中心掉的位置
    //      if (mPivotX == 0 && mPivotY == 0) {
    //            t.getMatrix().setScale(sx, sy);
    //         } 
    //        else {
    //            t.getMatrix().setScale(sx, sy, scale * mPivotX, scale * mPivotY);
    //         }
            }


        @Override
        public boolean getTransformation(long currentTime,
                Transformation outTransformation) {
            System.out.println("ScaleAndAlphaAnimation.getTransformation()");
            return super.getTransformation(currentTime, outTransformation);
        }

        @SuppressLint("NewApi") @Override
        protected float getScaleFactor() {
            return 0.5f;
        }


        @Override
        public void initialize(int width, int height, int parentWidth,
                int parentHeight) {
            super.initialize(width, height, parentWidth, parentHeight);
            System.out.println("ScaleAndAlphaAnimation.initialize()");
        }
    }

log输出如下:从这里可以看出,这几个方法执行的顺序是
构造函数–>initialize()–>接着就是applyTransformation()和getTransformation()的重复执行到动画结束了。(先applyTransformation()后getTransformation())


    07-12 06:42:57.958: I/System.out(4992): ScaleAndAlphaAnimation.ScaleAndAlphaAnimation()
    07-12 06:42:57.958: I/System.out(4992): ScaleAndAlphaAnimation.initialize()
    07-12 06:42:57.958: I/System.out(4992): ScaleAndAlphaAnimation.applyTransformation()
    07-12 06:42:57.958: I/System.out(4992): ScaleAndAlphaAnimation.getTransformation()
    07-12 06:42:57.958: I/System.out(4992): ScaleAndAlphaAnimation.applyTransformation()
    07-12 06:42:57.968: I/System.out(4992): ScaleAndAlphaAnimation.getTransformation()
    07-12 06:42:57.968: I/System.out(4992): ScaleAndAlphaAnimation.applyTransformation()
    07-12 06:42:57.998: I/System.out(4992): ScaleAndAlphaAnimation.getTransformation()
    07-12 06:42:57.998: I/System.out(4992): ScaleAndAlphaAnimation.applyTransformation()
    07-12 06:42:58.028: I/System.out(4992): ScaleAndAlphaAnimation.getTransformation()

OK,这篇就介绍到这里,下篇继续分析动画。

源码将会在下篇一起给。

你可能感兴趣的:(Android进阶)