美文网首页Android进阶
Android动画原理分析

Android动画原理分析

作者: htkeepmoving | 来源:发表于2019-01-28 22:12 被阅读0次

    前言

    《Android开发艺术探索》第三章弹性滑动中有这么一段话:”如何实现弹性滑呢?实现方法有很多,但它们都有一个共同的思想:将一次大的滑动分成若干次小的滑动并在一个时间段内完成,其实现方式有很多种,Scroller、Handler.postDelayed()、Thread.sleep()等“;我们将这段话套用到Android动画上:将一次大的属性变化(如透明度从1到0)分成很多次小的属性变化并在一个时间段内完成(16.7ms内完成),从而实现透明度渐变动画效果,其实现方式是通过Choreographer来完成的;Choreographer是动画原理的一个核心,搞明白动画工作原理之前我们需要先了解Choreographer工作过程;
    Choreographer工作过程简单描述如下:在Choreographer对象中有四条链表,分别保存着待处理的输入事件,待处理的动画事件,待处理的View绘制事件、待处理的post到Choreographer中事件,Android系统每隔16.7ms发出VSYNC信号,Choreographer.FrameDisplayEventReceiver收到信号后调用onVsync方法,最终会调用Choreographer.doFrame()方法,在doFrame方法中处理输入事件、动画事件、View绘制、post到Choreographer中的FrameCallback;我们可以使用Choreographer#postFrameCallback设置callback与Choreographer交互,设置的FrameCallCack(doFrame方法)会在下一个frame被渲染时触发(即下一个VSYNC到来时执行);Choreographer更多内容请参考Android Choreographer 源码分析
    Android动画原理简单描述:将View的一次大的属性变化拆分为多次小的属性变化,在每次VSYNC信号到来时,根据当前时间和插值器来计算当前View属性的值,然后给View设置该属性值,直到动画执行完毕。其中Choreographer将动画拆分成一次次小的属性变化,Choreographer是动画的指挥者。理想情况下,属性刷新次数(动画拆分为多次小的属性变化) = 动画执行时间/16.7ms 。
    我们通过属性动画工作流程来介绍Android动画工作原理,其它动画的工作过程也应该类似,感兴趣的可以阅读源码来了解其原理;本文主要围绕以下四个问题来讲解动画原理:
    问题一:动画如何完成一次属性变化刷新?
    问题二:动画如何被拆分成一次次小的属性变化?
    问题三:动画如何跳出属性刷新的流程,从而结束动画?
    问题四:动画监听器何时被调用?

    属性动画简单例子

    在讲解原理前,我们先来看一下属性的动画的一个简单例子:

    public class AnimationActivity extends AppCompatActivity implements View.OnClickListener{
    
        private Button mButton;
        public static final String TAG = "AnimationTest";
        @Override
        protected void onCreate(@Nullable Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.animation_layout);
            mButton = (Button) findViewById(R.id.button);
            mButton.setOnClickListener(this);
        }
    
        @Override
        public void onClick(View v) {
            //ObjectAnimator extends ValueAnimator;
            ValueAnimator alphaAnimation = ObjectAnimator.ofFloat(mButton,"alpha",1.0f,0f);
            alphaAnimation.setDuration(100);
            alphaAnimation.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                @Override
                public void onAnimationUpdate(ValueAnimator animation) {
                    //属性刷新
                    Log.d(TAG, "onAnimationUpdate: " + animation.getAnimatedValue());
                }
            });
            alphaAnimation.addListener(new Animator.AnimatorListener() {
                @Override
                public void onAnimationStart(Animator animation) {
                    //动画开始
                    Log.d(TAG, "onAnimationStart: ");
                }
    
                @Override
                public void onAnimationEnd(Animator animation) {
                    //动画结束
                    Log.d(TAG, "onAnimationEnd: ");
                }
    
                @Override
                public void onAnimationCancel(Animator animation) {
                    Log.d(TAG, "onAnimationCancel: ");
                }
    
                @Override
                public void onAnimationRepeat(Animator animation) {
                    Log.d(TAG, "onAnimationRepeat: ");
                }
            });
            alphaAnimation.start();
        }
    }
    

    运行之后,我们点击Button,对Button开始做透明度动画,相关日志打印如下:

    01-28 10:15:39.587 3867-3867/com.android.animation D/AnimationTest: onAnimationStart: 
    01-28 10:15:39.587 3867-3867/com.android.animation D/AnimationTest: onAnimationUpdate: 1.0
    01-28 10:15:39.597 3867-3867/com.android.animation D/AnimationTest: onAnimationUpdate: 1.0
    01-28 10:15:39.607 3867-3867/com.android.animation D/AnimationTest: onAnimationUpdate: 0.93037105
    01-28 10:15:39.627 3867-3867/com.android.animation D/AnimationTest: onAnimationUpdate: 0.75452065
    01-28 10:15:39.647 3867-3867/com.android.animation D/AnimationTest: onAnimationUpdate: 0.5
    01-28 10:15:39.657 3867-3867/com.android.animation D/AnimationTest: onAnimationUpdate: 0.24547923
    01-28 10:15:39.677 3867-3867/com.android.animation D/AnimationTest: onAnimationUpdate: 0.061846733
    01-28 10:15:39.697 3867-3867/com.android.animation D/AnimationTest: onAnimationUpdate: 0.0
    01-28 10:15:39.697 3867-3867/com.android.animation D/AnimationTest: onAnimationEnd: 
    

    动画原理分析

    问题一:动画如何完成一次属性变化刷新?
    下面我们从动画的入口alphaAnimation.start()开始分析动画原理,先来看动画如何完成一次属性刷新:

        private void start(boolean playBackwards) {
            //设置相关标志位
            mStarted = true;
            mPaused = false;
            mRunning = false;
            mAnimationEndRequested = false;
            //最关键的方法,动画循环执行在该方法中实现
            AnimationHandler animationHandler = AnimationHandler.getInstance();
            animationHandler.addAnimationFrameCallback(this, (long) (mStartDelay * sDurationScale));
    
            if (mStartDelay == 0 || mSeekFraction >= 0) {
                // If there's no start delay, init the animation and notify start listeners right away
                // to be consistent with the previous behavior. Otherwise, postpone this until the first
                // frame after the start delay.
                //初始化相关属性,onAnimationStart在该方法中被调用;
                startAnimation();
            }
        }
    

    以上最核心的方法是AnimationHandler.addAnimationFrameCallback()。下面我们来分析该方法:

    AnimationHandler
        //final AnimationFrameCallback callback就是ValueAnimation
        //public class ValueAnimator extends Animator implements AnimationHandler.AnimationFrameCallback
        public void addAnimationFrameCallback(final AnimationFrameCallback callback, long delay) {
            if (mAnimationCallbacks.size() == 0) {
                //动画开始执行时,mAnimationCallbacks.size()为0.会调用getProvider().postFrameCallback方法;
                getProvider().postFrameCallback(mFrameCallback);
            }
            if (!mAnimationCallbacks.contains(callback)) {
                //将ValueAnimator添加在mAnimationCallbacks列表;
                mAnimationCallbacks.add(callback);
            }
        }
    

    下面我们来看getProvider().postFrameCallback(mFrameCallback);
    AnimationHandler.getProvider()方法、MyFrameCallbackProvider、mFrameCallback代码如下:

    private AnimationFrameCallbackProvider getProvider() {
            if (mProvider == null) {
                mProvider = new MyFrameCallbackProvider();
            }
            return mProvider;
        }
        
         /**
         * Default provider of timing pulse that uses Choreographer for frame callbacks.
         */
        private class MyFrameCallbackProvider implements AnimationFrameCallbackProvider {
            //最核心的大佬编舞者终于现身了
            final Choreographer mChoreographer = Choreographer.getInstance();
    
            @Override
            public void postFrameCallback(Choreographer.FrameCallback callback) {
                mChoreographer.postFrameCallback(callback);
            }
    
            @Override
            public void postCommitCallback(Runnable runnable) {
                mChoreographer.postCallback(Choreographer.CALLBACK_COMMIT, runnable, null);
            }
    
            @Override
            public long getFrameTime() {
                //获取上一次VSYNC信号时间;
                return mChoreographer.getFrameTime();
            }
        }
        
        private final Choreographer.FrameCallback mFrameCallback = new Choreographer.FrameCallback() {
            @Override
            public void doFrame(long frameTimeNanos) {
                //Choreographer收到VSYNC都会调用该方法,正常情况下每16.7ms就会调用一次doFrame
                doAnimationFrame(getProvider().getFrameTime());
                if (mAnimationCallbacks.size() > 0) {
                    //再将自己post到编舞者中,以便下次收到VSYNC时,继续调用doFrame来刷新动画;
                    getProvider().postFrameCallback(this);
                }
            }
        };
    

    在执行完getProvider().postFrameCallback(mFrameCallback)后,下一次收到VYSNC信号时,就会调用mFrameCallback.doFrame方法,doFrame方法主要做了两件重要的工作:
    (1)调用doAnimationFrame刷新View的相关属性;
    (2)将自己再次post到Choreographer中,这样在下一次VSYNC到来时继续执行doFrame,完成下一次属性刷新;
    关键点:动画属性一次次刷新就是通过mFrameCallback来实现的;
    下面我们来看doAnimationFrame(getProvider().getFrameTime())方法:

        private void doAnimationFrame(long frameTime) {
            for (int i = 0; i < size; i++) {
                //callback就是我们添加到mAnimationCallbacks列表中的ValueAnimator
                final AnimationFrameCallback callback = mAnimationCallbacks.get(i);
                if (isCallbackDue(callback, currentTime)) {
                    //执行ValueAnimator.doAnimationFrame;
                    callback.doAnimationFrame(frameTime);
                    if (mCommitCallbacks.contains(callback)) {
                        getProvider().postCommitCallback(new Runnable() {
                            @Override
                            public void run() {
                                //每一次刷新刷新之后会调用,目前没有看到会执行该方法;
                                commitAnimationFrame(callback, getProvider().getFrameTime());
                            }
                        });
                    }
                }
            }
            cleanUpList();
        }
    

    AnimationFrameCallback callback = mAnimationCallbacks.get(i);该callback就是valueAnimator,即我们在调用AnimationHandler.addAnimationFrameCallback中将ValueAnimator添加在mAnimationCallbacks列表;
    下面我们来看ValueAnimator.doAnimationFrame(frameTime)方法,在该方法最终会刷新View的属性;

     /**
         * Processes a frame of the animation, adjusting the start time if needed.
         *  处理每一帧中的刷新
         * @param frameTime The frame time.
         * @return true if the animation has ended.
         * @hide
         */
        public final void doAnimationFrame(long frameTime) {
            //在animateBasedOnTime刷新View属性,并判断动画是否执行完毕;
            boolean finished = animateBasedOnTime(currentTime);
            if (finished) {
                //如果动画结束执行endAnimation
                endAnimation();
            }
        }
    

    下面我们继续来看animateBasedOnTime()方法:

        boolean animateBasedOnTime(long currentTime) {
            //done表示动画是否执行完毕;
            boolean done = false;
            //在ValueAnimator.startAnimation中将mRuning置为true;
            if (mRunning) {
                final long scaledDuration = getScaledDuration();
                final float fraction = scaledDuration > 0 ?
                        (float)(currentTime - mStartTime) / scaledDuration : 1f;
                final float lastFraction = mOverallFraction;
                final boolean newIteration = (int) fraction > (int) lastFraction;
                //根据当前时间currentTime来判断动画是否执行完毕;
                final boolean lastIterationFinished = (fraction >= mRepeatCount + 1) &&
                        (mRepeatCount != INFINITE);
                if (scaledDuration == 0) {
                    ...
                } else if (newIteration && !lastIterationFinished) {
                    ...
                } else if (lastIterationFinished) {
                    //动画执行完毕;
                    done = true;
                }
                //计算当前View属性值,然后设置View相关属性;
                animateValue(currentIterationFraction);
            }
            return done;
        }
    

    我们继续来看ObjectAnimator和ValueAnimator的animateValue方法,ObjectAnimator继承ValueAnimator;

    ObjectAnimator:
        void animateValue(float fraction) {
            //getTarget()返回的就是ValueAnimator alphaAnimation = ObjectAnimator.ofFloat(mButton,"alpha",1.0f,0f)中的mButton,
            final Object target = getTarget();
            //调用ValueAnimator.animateValue,主要是计算当前动画的进度
            super.animateValue(fraction);
            int numValues = mValues.length;
            for (int i = 0; i < numValues; ++i) {
                //调用PropertyValuesHolder.setAnimatedValue设置mButton在该帧的透明度,即super.animateValue(fraction)计算出的当前透明度
                mValues[i].setAnimatedValue(target);
            }
        }
    VauleAnimator:
        void animateValue(float fraction) {
            //根据插值器计算当前属性动画的进度
            fraction = mInterpolator.getInterpolation(fraction);
            mCurrentFraction = fraction;
            int numValues = mValues.length;
            for (int i = 0; i < numValues; ++i) {
                //将当前进度存放到PropertyValuesHolder.mAnimatedValue,
                //在AnimatorUpdateListener.onAnimationUpdate方法中可以通过ValueAnimator.getAnimatedValue获取动画进度
                mValues[i].calculateValue(fraction);
            }
            if (mUpdateListeners != null) {
                int numListeners = mUpdateListeners.size();
                for (int i = 0; i < numListeners; ++i) {
                    //回调AnimatorUpdateListener.onAnimationUpdate方法
                    mUpdateListeners.get(i).onAnimationUpdate(this);
                }
            }
        }
    

    animateValue方法主要工作:根据插值器计算mButton在该帧时的透明度,然后调用PropertyValuesHolder.setAnimatedValue设置mButton的透明度,从而完成该帧透明度变化;每一帧更新一次透明度,最终完成整个透明度变化过程;
    下面我们来看PropertyValuesHolder.setAnimatedValue(Object target)代码,其中target就是ValueAnimator alphaAnimation = ObjectAnimator.ofFloat(mButton,"alpha",1.0f,0f)中的mButton,该方法主要就是设置当前View的属性值,执行该方法最终会调用View.setAlpha()来设置mButton的透明度,在该帧中mButton的透明度就完成了更新。

    /**
         * Internal function to set the value on the target object, using the setter set up
         * earlier on this PropertyValuesHolder object. This function is called by ObjectAnimator
         * to handle turning the value calculated by ValueAnimator into a value set on the object
         * according to the name of the property.
         * @param target The target object on which the value is set
         *  通过注释可以看出,该方法主要就是设置当前View的属性值,执行完该方法后,在该帧中mButton的透明度就完成了更新
         */
        void setAnimatedValue(Object target) {
            // 如果有属性,通过set方法来更新属性值
            if (mProperty != null) {
                //getAnimatedValue()就是返回VauleAnimator.animateValue中计算出的当前进度值
                mProperty.set(target, getAnimatedValue());
            }
            // 是否通过反射调用属性的setter方法来更新属性值
            if (mSetter != null) {
                try {
                    //getAnimatedValue()就是返回VauleAnimator.animateValue中计算出的当前进度值    
                    mTmpValueArray[0] = getAnimatedValue();
                    mSetter.invoke(target, mTmpValueArray);
                }
            }
        }
    

    以上流程完成了mButton透明度的一次更新,同时也解释了动画是如何完成一次属性变化的刷新。
    问题二:动画如何被拆分成一次次小的属性变化?
    该问题也可以简单理解如何循环更新mButton透明度呢?我们再来回顾AnimationHandler.addAnimationFrameCallback方法中:

    public void addAnimationFrameCallback(final AnimationFrameCallback callback, long delay) {
            if (mAnimationCallbacks.size() == 0) {
                getProvider().postFrameCallback(mFrameCallback);
            }
        }
        
        private final Choreographer.FrameCallback mFrameCallback = new Choreographer.FrameCallback() {
            @Override
            public void doFrame(long frameTimeNanos) {
                //在doAnimationFrame更新mButton在当前帧中的透明度
                doAnimationFrame(getProvider().getFrameTime());
                //mAnimationCallbacks列表中存放正在执行的动画,动画执行完毕会将对应的AnimationFrameCallback remove掉
                if (mAnimationCallbacks.size() > 0) {
                    //将mFrameCallback自身再次post到Choreographer在下一次VSYNC信号到来时,再次调用doFrame来更新mButton的透明度
                    //如果动画一直没有执行完毕,会一直循环执行doFrame
                    getProvider().postFrameCallback(this);
                }
            }
        };
    

    在mFrameCallback中完成多次动画的拆分,并在每次收到VSYNC信号时,更新View的属性,连续不停的更新View属性,直到动画结束。
    问题三:动画如何跳出属性刷新的流程,从而结束动画?
    该问题也可以理解为动画是如何跳出mFrameCallback循环,在mFrameCallback中会判断mAnimationCallback列表中是否还有AnimationFrameCallback,如果mAnimationCallback列表中没有AnimationFrameCallback,那么透明度变化在下一次VSYNC到来时就不会再继续执行从而结束动画,那么我们找到mAnimationCallbacks列表啥时候将AnimationFrameCallback remove掉,就代表动画结束;主要流程如下:

    ValueAnimator:
        public final void doAnimationFrame(long frameTime) {
            //animateBasedOnTime根据当前时间计算动画是否应该结束
            //如果动画应该结束,返回true;
            boolean finished = animateBasedOnTime(currentTime);
            if (finished) {
                //调用endAnimation结束动画
                endAnimation();
            }
        }
        
        private void endAnimation() {
            AnimationHandler handler = AnimationHandler.getInstance();
            //在AnimationHandler将ValueAnimator remove掉;
            handler.removeCallback(this);
            if ((mStarted || mRunning) && mListeners != null) {
                for (int i = 0; i < numListeners; ++i) {
                    //调用AnimatorListener.onAnimationEnd方法通知应用
                    tmpListeners.get(i).onAnimationEnd(this);
                }
            }
        }
    AnimationHandler:
        public void removeCallback(AnimationFrameCallback callback) {
            int id = mAnimationCallbacks.indexOf(callback);
            if (id >= 0) {
                //将ValueAnimator remove调用,这样在下一此VSYNC到来时就不会继续执行动画
                mAnimationCallbacks.set(id, null);
            }
        }
    

    问题四:动画监听器何时被调用?
    即以下几个方法是在何时回调的?
    AnimatorListener.onAnimationStart(); //在动画开始时调用
    AnimatorUpdateListener.onAnimationUpdate();//在每一次属性刷新时调用
    AnimatorListener.onAnimationEnd();//动画结束时调用
    通过以上代码中注释我们可以找到以下调用关系:

    ValueAnimator.start() > startAnimation() > notifyStartListeners > AnimatorListener.onAnimationStart()
    ValueAnimator.doAnimationFrame() > animateBasedOnTime() > AnimatorListener.onAnimationUpdate();
    ValueAnimator.doAnimationFrame() > endAnimation() > AnimatorListener.onAnimationEnd();
    

    最后,我们再来回顾一下动画原理的简单描述:将View的一次大的属性变化拆分为多次小的属性变化,在每次VSYNC信号到来时,根据当前时间和插值器来计算当前View属性的值,然后设置给View设置该属性值,直到动画执行完毕,动画的拆分是由Choreographer来完成的。
    以上就是动画执行的主要过程,希望对你理解Android动画有所帮助。

    参考资料

    《Android开发艺术探索》
    《深入理解Android卷三》第四章Android动画原理简介
    Android动画原理分析

    相关文章

      网友评论

        本文标题:Android动画原理分析

        本文链接:https://www.haomeiwen.com/subject/gnvzjqtx.html