Android动画原理分析

前言

《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动画原理分析

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,736评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,167评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,442评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,902评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,302评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,573评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,847评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,562评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,260评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,531评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,021评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,367评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,016评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,068评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,827评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,610评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,514评论 2 269

推荐阅读更多精彩内容