Android O Framework架构分析:属性动画ValueAnimator机制分析

一. 概述

        属性动画是app开发中常用的一种动画方式。传统的补间动画主要有以下缺点:
        (1) 作用对象局限于View
        (2) 动画效果单一,仅能实现位移、旋转、缩放、透明度四种属性的改变
        (3) 没有改变View真实属性
        在Android3.0以后引入了这种动画模式,用来弥补传统的补间动画和帧动画的不足。属性动画的核心类如下图所示:


        其中Animator是属性动画的基类,提供了一些通用的方法。AnimatorSet相当于一组属性动画的容器,用来同时执行多个属性动画。app开发中常用的属性动画为ValueAnimator和ObjectAnimator,本文将基于Android O的版本详细介绍ValueAnimator在系统中的实现方式。

二. 基本流程分析

1.使用方法

private void testValueAnimator() {
    ValueAnimator valueAnimator = ValueAnimator.ofInt(0, 100);
    valueAnimator.setDuration(300);
    valueAnimator.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) {
  
        }
  
        @Override
        public void onAnimationRepeat(Animator animation) {
  
        }
    });
  
    valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
        @Override
        public void onAnimationUpdate(ValueAnimator animation) {
            Log.d(TAG, "fraction: " + animation.getAnimatedFraction() + " value: "
                    + animation.getAnimatedValue());
        }
    });
  
    Log.d(TAG, "testValueAnimator");
    valueAnimator.setStartDelay(100);
    valueAnimator.start();
}
        其主要分为以下几个过程:
        (1) 创建ValueAnimator对象 => 通过ofXXX的静态方法获取
        (2) 初始化参数 => 根据一系列setXXX/addXXX方法添加一些初始化参数和回调函数
        (3) 开始动画 => 调用start方法
        在使用属性动画时,客户端通过ValueAnimator.AnimatorUpdateListener接口的回调函数onAnimationUpdate来监听每一帧的变化。属性动画顾名思义,在动画过程中每一帧都会对预设的属性进行改变,客户端通过每一帧该属性的变化自定义一些操作。其预设的属性就是在获取ValueAnimator对象时,通过ofXXX方法设置的。上例中的ValueAnimator.ofInt(0, 100)则表示:在该属性动画执行的过程中,一个属性名为空字符串(默认属性名,后面会说到属性动画的属性名何时为默认,何时为开发者自定义),属性类型为整型的属性在0 ~ 100区间内递增的变化。在每一帧的回调中,开发者可以通过ValueAnimator#getAnimatedValue方法获取默认属性值在该帧的值为多少。上例中的另一个方法:ValueAnimator#getAnimatedFraction则表示该帧的动画进度(0 ~ 1浮点数)为多少。
2.动画初始化过程

        (1) ofXXX方法:设置动画属性类型及变化范围

 

        初始化提供的方法有:ofInt,ofFloat,ofArgb(描述颜色的ARGB值的变化),ofPropertyValuesHolder(自定义的一系列PropertyValuesHolder,使用该方法时,会在动画每一帧分别处理添加进来的PropertyValuesHolder的变化),ofObject(使用该方法时需要自定义类型估值器)。ofInt,ofFloat,ofObject,ofArgb的实质都是先包装出一个对应的PropertyValuesHolder对象,然后当动画驱动时,根据这个对象计算数当前动画的进度(fraction)以及对应类型值在这一帧的取值(animated value)。ofPropertyValuesHolder方法相当于开发者自己创建若干PropertyValuesHolder对象,在动画驱动时批处理这些对象。
        如果使用ofPropertyValuesHolder,需要开发者自己创建PropertyValuesHolder对象,需要传入对应改变的属性的名字。如果使用其它初始化方法,则属性名字默认为空字符串("")。客户端通过ValueAnimator#getAnimatedValue方法获取每一帧的属性值为多少:如果调用无参的该方法,则返回的是默认的属性值在当前帧的值,因为默认的初始化方法确定了其属性类型,并且只有一个。如果调用的是带String类型参数的该方法,则返回的是对应名字的属性值在当前帧的值,其名字是在创建PropertyValuesHolder对象时设置的。另外,开发者也可以通过ValueAnimator#setValues方法主动添加PropertyValuesHolder对象进来。
        综上,无论通过哪种ofXXX方法进行初始化,最终都会创建一个或多个PropertyValuesHolder对象,这个类是用来管理属性动画中开发者定义的“属性”的变化,每当动画进行时,ValueAnimator对象会计算所有持有的PropertyValuesHolder对象在此帧的属性值为多少,开发者可以通过getAnimatedValue获取。属性动画的核心也在于此——动画驱动过程中,开发者可以自定义任意类型的对象的变化,因此属性动画也打破了补间动画的局限性。
        (2) addUpdateListener:添加一个AnimatorUpdateListener的监听者
        这个监听者的作用在于:在动画驱动过程中,每一帧都会通过该回调通知给客户端进程,客户端可以在该回调中处理每一帧要做的事情。
        (3) addListener:添加一个AnimatorListener的监听者
        这个监听者用来监听动画的开始/结束/取消/重复四个行为的发生。
        (4) setDuration:添加动画执行时长
        (5) setCurrentFraction/setCurrentPlayTime
        设置动画开始的动画进度/开始的时间点(0 ~ Duration范围内)

3.动画启动过程:ValueAnimator#start()


        动画启动过程的关键流程:(step表示上图中的步骤序号)
        step4:向Choreographer中注册一个动画回调,用来驱动整个动画流程。
        step6 ~ step7:初始化PropertyValuesHolder对象中的类型估值器,默认只支持Int/Float两种类型的类型估值器,其他类型需要自定义。
        step8:通知客户端注册的监听者动画开始:AnimatorListener#onAnimationStart被回调。
        step9 ~ step10:设置动画开始时间以及开始时动画进度,如果客户端没有主动通过setCurrentFraction/setCurrentPlayTime设置,则默认会调用setCurrentPlayTime(0),表示动画从头开始。
        step13 ~ step15:入口为animateValue,每一帧的动画都会调用到此方法,该方法主要完成以下几件事:
        (1) 根据时间进度以及动画插值器计算出当前动画进度(fraction)
        (2) 根据当前动画进度以及类型估值器计算出当前PropertyValuesHolder在此帧中的取值是多少(animated value)
        (3) 回调AnimatorUpdateListener#onAnimationUpdate方法
        启动时会调用该方法,认为start方法触发的行为为第一帧(Choreographer驱动的为第二帧)

4.动画驱动过程:Choreographer#doFrame


        与补间动画一样,属性动画仍然是通过向Choreographer注册回调来监听Vsync信号。其中step5 ~ step7与动画启动时的逻辑相同:计算当前帧的动画进度与当前属性值。

三. 关键流程源码分析

        下面我们仍然根据动画的初始化 → 启动 → 驱动三个过程来分析其关键步骤的源码。
1.初始化过程
        上面我们得知,ValueAnimator开始时通过ofXXX方法来获取一个实例,其主要分为两类:
        ofInt/ofFloat/ofArgb/ofObject:通过这四个方法进行初始化,ValueAnimator会为其创建一个默认的PropertyValuesHolder对象,并且其属性名默认为空字符串("")。
        ofPropertyValuesHolder:开发者自己创建若干PropertyValuesHolder对象,需要自定义属性的类型、名字、以及变化区间(setValues和该方法一样)。

        虽然说是两种方式,但其核心都是创建一个或多个属性。以其中一种方式为例(ValueAnimator.java#ofInt):

public static ValueAnimator ofInt(int... values) {
    // 创建一个ValueAnimator对象
    ValueAnimator anim = new ValueAnimator();
    // 这里的values应该是开发者传入的关键帧的属性取值,上例子中传入的是(0, 100),则表示
    // 整个动画过程中有两个关键帧(首帧和尾帧),这两帧的取值为0和100
    // 动画过程中每一帧的属性取值都是根据动画进度以及关键帧、类型估值器三个因素计算出来的,后续会详细说明
    anim.setIntValues(values);
    return anim;
}
  
  
public void setIntValues(int... values) {
    if (values == null || values.length == 0) {
        return;
    }
    // mValues是当前这个ValuesAnimator对象记录的所有属性(PropertyValuesHolder对象)的List
    if (mValues == null || mValues.length == 0) {
        // 如果当前没有添加任何属性,则创建一个属性名字为空字符串,类型为int型的属性
        setValues(PropertyValuesHolder.ofInt("", values));
    } else {
        // 这里的逻辑比较奇怪,如果已经有持有的属性了,则会把当前持有的第一个属性类型修改为整型,并更新关键帧
        PropertyValuesHolder valuesHolder = mValues[0];
        valuesHolder.setIntValues(values);
    }
    // New property/values/target should cause re-initialization prior to starting
    mInitialized = false;
}
  
  
// 创建出来的属性对象将会通过该方法记录到ValueAnimator对象中
public void setValues(PropertyValuesHolder... values) {
    int numValues = values.length;
    mValues = values;
    // 这个map的key为属性名字,value为对应属性
    mValuesMap = new HashMap<String, PropertyValuesHolder>(numValues);
    for (int i = 0; i < numValues; ++i) {
        PropertyValuesHolder valuesHolder = values[i];
        mValuesMap.put(valuesHolder.getPropertyName(), valuesHolder);
    }
    // New property/values/target should cause re-initialization prior to starting
    mInitialized = false;
}

      PropertyValuesHolder用来计算每一帧该属性的取值为多少,仍然以创建int类型属性为例,其创建过程为(PropertyValuesHolder.java):

// 初始化方法有两个重载函数,不同之处在于:第一个直接传入属性名字,PropertyHolderValues会记录其属性名字以及属性类型
public static PropertyValuesHolder ofInt(String propertyName, int... values) {
    return new IntPropertyValuesHolder(propertyName, values);
}
  
  
// 第二个函数需要调用者自己传入一个Property对象,但属性类型必须是Integer类型的(第二个泛型表示其属性类型)
public static PropertyValuesHolder ofInt(Property<?, Integer> property, int... values) {
    return new IntPropertyValuesHolder(property, values);
}
  
  
// 创建一个整型属性对象:IntPropertyValuesHolder是PropertyValusHolder的嵌套类,它继承了PropertyValusHolder
public IntPropertyValuesHolder(String propertyName, int... values) {
    // 先调用基类PropertyValuesHolder的构造函数
    super(propertyName);
    // 设置关键帧的属性取值
    setIntValues(values);
}
  
  
// PropertyValuesHolder的构造方法
private PropertyValuesHolder(String propertyName) {
    // 记录属性名字
    mPropertyName = propertyName;
}
  
  
// 接着调用IntPropertyValuesHolder的setIntValues方法
@Override
public void setIntValues(int... values) {
    // 仍然先调用基类的setIntValues方法
    super.setIntValues(values);
    // 基类的setIntValues会根据传入的关键帧属性取值为mKeyframes赋值,这里将基类的值转型后记录在子类中
    mIntKeyframes = (Keyframes.IntKeyframes) mKeyframes;
}
  
  
// PropertyValuesHolder的setIntValues方法
public void setIntValues(int... values) {
    mValueType = int.class;
    // 注意这里创建了一个KeyframeSet的对象,这个类用来管理关键帧
    mKeyframes = KeyframeSet.ofInt(values);
}

        通过方法调用栈看比较乱,下面是该过程的时序图:


        根据上面代码流程的分析,我们知道PropertyValuesHolder中有以下几个重要的用来管理属性的变量:
        mPropertyName → 记录该属性的名字
        mProperty → 封装的Property对象,其记录了属性的名字及属性的类型(该成员不一定被赋值)
        mKeyframes → Keyframes对象,关键帧,用来记录指定动画进度对应的时间点的属性取值应该是多少

        KeyFrame的初始化过程如下(KeyframeSet.java):

public static KeyframeSet ofInt(int... values) {
    int numKeyframes = values.length;
    IntKeyframe keyframes[] = new IntKeyframe[Math.max(numKeyframes,2)];
    // 这里要对比Keyframe的ofInt方法来看
    // 传入的浮点数表示fraction,从上文中我们可以知道这表示动画进度
    // 第二个数表示对应进度的属性值为多少
    if (numKeyframes == 1) {
        // 当只传入一个数值时,这里比较好理解,动画进度为0时,属性值为0(默认值)
        // 动画进度为1时(动画结束),属性值为传入的值
        // 所以当传入一个值时,关键帧默认为0, 1(以动画进度表示关键帧位置)
        keyframes[0] = (IntKeyframe) Keyframe.ofInt(0f);
        keyframes[1] = (IntKeyframe) Keyframe.ofInt(1f, values[0]);
    } else {
        // 传入多个数值时,则等比例计算动画进度,并记录对应的属性值
        // 比如传入3个数(0, 50, 70, 100),根据此处的计算结果,记录的值为:
        // 动画进度对应的属性取值为0 -> 1, 0.33 -> 50, 0.67 -> 70, 1 -> 100
        // 此时的四个关键帧为:0, 0.33, 0.67, 1
        keyframes[0] = (IntKeyframe) Keyframe.ofInt(0f, values[0]);
        for (int i = 1; i < numKeyframes; ++i) {
            keyframes[i] =
                    (IntKeyframe) Keyframe.ofInt((float) i / (numKeyframes - 1), values[i]);
        }
    }
    return new IntKeyframeSet(keyframes);
}

      上述逻辑可以清晰地看出,这个类记录了关键帧的属性取值,不过关键帧是通过动画进度来表示的。如果没有通过PropertyValuesHolder.ofKeyframe方法来指定关键帧位置,则默认按照传入的values个数等分计算关键帧位置。
        除了开始的ofXXX方法,还会有一些其它的方法用来初始化,但过程比较简单,都是一些赋值操作,涉及到ValueAnimator中的一些变量为:
        mDuration → 表示动画的执行时间,通过setDuration设置
        mSeekFraction → 动画开始时的进度,默认为0,通过setCurrentPlayTime/setCurrentFraction设置
        mStartDelay → 动画延时启动时间,默认为0,通过setStartDelay设置
        mUpdateListeners → 动画每一帧执行时的监听者,用来通知客户端每一帧的发生,通过addUpdateListener设置
        mListeners → 动画开始、结束、重复、取消四个行为的监听者,通过addListener设置
2.动画启动过程
        动画启动过程通过客户端调用ValueAnimator#start方法开始(ValueAnimator.java):

// playBackwards当前是否处于动画反转(reverse)过程,正常通过start启动时会默认传递false进来
private void start(boolean playBackwards) {
    // 该方法必须在Looper线程中调用
    if (Looper.myLooper() == null) {
        throw new AndroidRuntimeException("Animators may only be run on Looper threads");
    }
    mReversing = playBackwards;
    mSelfPulse = !mSuppressSelfPulseRequested;
    // Special case: reversing from seek-to-0 should act as if not seeked at all.
    // 这是特殊情况,如果预设了初始的动画进度值,并且处于反转状态,则需要重新计算初始动画进度
    if (playBackwards && mSeekFraction != -1 && mSeekFraction != 0) {
        if (mRepeatCount == INFINITE) {
            // Calculate the fraction of the current iteration.
            float fraction = (float) (mSeekFraction - Math.floor(mSeekFraction));
            mSeekFraction = 1 - fraction;
        } else {
            mSeekFraction = 1 + mRepeatCount - mSeekFraction;
        }
    }
    mStarted = true;
    mPaused = false;
    mRunning = false;
    mAnimationEndRequested = false;
    // Resets mLastFrameTime when start() is called, so that if the animation was running,
    // calling start() would put the animation in the
    // started-but-not-yet-reached-the-first-frame phase.
    mLastFrameTime = -1;
    mFirstFrameTime = -1;
    mStartTime = -1;
    // 在文章最开始的时候的时序图中介绍了该方法,这个方法用来向Choreographer中注册动画回调,以达到驱动动画的目的
    addAnimationCallback(0);
  
    // 没有设置延时启动,或者设置了初始动画进度,或者处于反转状态
    if (mStartDelay == 0 || mSeekFraction >= 0 || mReversing) {
        // 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.
        // 启动动画
        startAnimation();
        if (mSeekFraction == -1) {
            // No seek, start at play time 0. Note that the reason we are not using fraction 0
            // is because for animations with 0 duration, we want to be consistent with pre-N
            // behavior: skip to the final value immediately.
            // 客户端没有指定动画开始进度,则设置开始进度为0
            setCurrentPlayTime(0);
        } else {
            setCurrentFraction(mSeekFraction);
        }
    }
}

        根据上述代码可以看到,start方法会触发下面的调用:
        addAnimationCallback
        如果没有设置延迟启动时间且没有设置动画进度,则还会调用下面两个方法: 
        startAnimation → setCurrentPlayTime(这个方法最终也会调setCurrentFraction)
        下面逐步分析这几个方法:

        (1) addAnimationCallback(ValueAnimator.java)

private void addAnimationCallback(long delay) {
    // mSelfPulse默认为true,表示该动画是否需要自己通过Choreographer注册动画回调来驱动
    if (!mSelfPulse) {
        return;
    }
    // 这里传入两个参数,第一个为ValueAnimator自身对象,它实现了
    // AnimationHandler.AnimationFrameCallback接口,用来执行每一帧动画的回调
    // 第二个delay默认为0
    getAnimationHandler().addAnimationFrameCallback(this, delay);
}
public void addAnimationFrameCallback(final AnimationFrameCallback callback, long delay) {
    // 这里是将全局的一个用来FrameCallback post到Choreographer中
    if (mAnimationCallbacks.size() == 0) {
        getProvider().postFrameCallback(mFrameCallback);
    }
    // 这里将刚才的callback加入到一个全局的List中
    if (!mAnimationCallbacks.contains(callback)) {
        mAnimationCallbacks.add(callback);
    }
  
    if (delay > 0) {
        mDelayedCallbackStartTime.put(callback, (SystemClock.uptimeMillis() + delay));
    }
}
        addAnimationFrameCallback方法将FrameCallback注册到Choreographer中,用来驱动整个动画过程。

        (2) startAnimation(ValueAnimator.java)

private void startAnimation() {
    if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
        Trace.asyncTraceBegin(Trace.TRACE_TAG_VIEW, getNameForTrace(),
                System.identityHashCode(this));
    }
  
    mAnimationEndRequested = false;
    initAnimation();
    mRunning = true;
    if (mSeekFraction >= 0) {
        mOverallFraction = mSeekFraction;
    } else {
        mOverallFraction = 0f;
    }
    if (mListeners != null) {
        notifyStartListeners();
    }
}
        这个过程主要干了两件重要的事情:

        initAnimation:通知ValueAnimator中持有的所有属性对象进行初始化操作,其逻辑很简单,遍历mValues数组,调用每一个PropertyValuesHolder对象的init方法去初始化(ProertyValuesHolder.java):

// 这个方法用来为属性添加类型估值器
void init() {
    if (mEvaluator == null) {
        // We already handle int and float automatically, but not their Object
        // equivalents
        // 系统默认为int型属性和float型属性创建了默认的类型估值器
        // 因此,当属性类型不是int/float类型时,需要客户端自定义类型估值器
        mEvaluator = (mValueType == Integer.class) ? sIntEvaluator :
                (mValueType == Float.class) ? sFloatEvaluator :
                null;
    }
    if (mEvaluator != null) {
        // KeyframeSet knows how to evaluate the common types - only give it a custom
        // evaluator if one has been set on this class
        mKeyframes.setEvaluator(mEvaluator);
    }
}

        这里简单介绍下类型估值器(TypeEvaluator):它的作用为:根据当前动画进度(fraction),取值区间(startValue, endValue)来返回当前进度的属性取值为多少。比如系统提供默认的整型类型估值器(IntEvaluator.java):

public class IntEvaluator implements TypeEvaluator<Integer> {
  
    /**
     * This function returns the result of linearly interpolating the start and end values, with
     * <code>fraction</code> representing the proportion between the start and end values. The
     * calculation is a simple parametric calculation: <code>result = x0 + t * (v1 - v0)</code>,
     * where <code>x0</code> is <code>startValue</code>, <code>x1</code> is <code>endValue</code>,
     * and <code>t</code> is <code>fraction</code>.
     *
     * @param fraction   The fraction from the starting to the ending values
     * @param startValue The start value; should be of type <code>int</code> or
     *                   <code>Integer</code>
     * @param endValue   The end value; should be of type <code>int</code> or <code>Integer</code>
     * @return A linear interpolation between the start and end values, given the
     *         <code>fraction</code> parameter.
     */
    public Integer evaluate(float fraction, Integer startValue, Integer endValue) {
        int startInt = startValue;
        return (int)(startInt + fraction * (endValue - startInt));
    }
}
        如上面代码所示,其计算方式非常简单,根据动画进度的百分比算出该区间内(这个区间根据关键帧计算得出)属性取值。如果不想使用线性的计算方式,我们可以自定义类型估值器,通过ValueAnimator#setEvaluator方法设置。
        notifyStartListeners:通知注册进来的回调,动画开始(Animator.AnimatorListener#onAnimationStart)
        (3) setCurrentPlayTime

        没有设置延时启动时,并且没有设置初始启动的动画进度时,则会传递0进去,表示动画从头开始(ValueAnimator.java):

public void setCurrentPlayTime(long playTime) {
    // 传入的是0的话,对应的动画进度也是0
    float fraction = mDuration > 0 ? (float) playTime / mDuration : 1;
    setCurrentFraction(fraction);
}
  
  
public void setCurrentFraction(float fraction) {
    // 上面分析过该方法,用来初始化属性的类估值器
    initAnimation();
    fraction = clampFraction(fraction);
    // 如果掉帧,则会导致mStartTime出现问题,这个变量与纠正mStartTime相关
    mStartTimeCommitted = true; // do not allow start time to be compensated for jank
    if (isPulsingInternal()) {
        long seekTime = (long) (getScaledDuration() * fraction);
        long currentTime = AnimationUtils.currentAnimationTimeMillis();
        // Only modify the start time when the animation is running. Seek fraction will ensure
        // non-running animations skip to the correct start time.
        mStartTime = currentTime - seekTime;
    } else {
        // If the animation loop hasn't started, or during start delay, the startTime will be
        // adjusted once the delay has passed based on seek fraction.
        mSeekFraction = fraction;
    }
    mOverallFraction = fraction;
    final float currentIterationFraction = getCurrentIterationFraction(fraction, mReversing);
    // 进行完一些初始化工作后,最终会调用此方法
    animateValue(currentIterationFraction);
}
  
  
@CallSuper
void animateValue(float fraction) {
    // 根据插值器,把时间进度转化为动画进度
    fraction = mInterpolator.getInterpolation(fraction);
    mCurrentFraction = fraction;
    int numValues = mValues.length;
    for (int i = 0; i < numValues; ++i) {
        // 遍历所有属性,根据动画进度计算其属性取值
        mValues[i].calculateValue(fraction);
    }
    if (mUpdateListeners != null) {
        int numListeners = mUpdateListeners.size();
        for (int i = 0; i < numListeners; ++i) {
            // 回调onAnimationUpdate方法
            mUpdateListeners.get(i).onAnimationUpdate(this);
        }
    }
}
        需要关注的是最后一个方法调用:animateValue,显然,这个方法应该是在动画驱动过程中不断被调用的。但是如果当属性动画启动时,没有设置延迟启动时间(mStartDelay)和当前动画进度值(mSeekFraction),则在start过程中也会调用到此方法,并且动画进度为0,在start过程中会触发到的客户端回调顺序为:Animator.AnimatorListener#onAnimationStart → ValueAnimator.AnimatorUpdateListener#onAnimationUpdate,这个过程会被当作动画的首帧。但是如果设置了延迟启动时间或者动画进度值,则start不触发这些逻辑,Choreographer的FrameCallback回调时才会触发相关逻辑。
3.动画的驱动过程

        如前文所述,动画通过Choreographer的FrameCallback驱动,FrameCallback在AnimationHandler中向Choreographer注册,是一个全局的FrameCallback对象(AnimationHandler.java):

// start方法结束后,下一个Vsync信号过来时会回调此方法
private final Choreographer.FrameCallback mFrameCallback = new Choreographer.FrameCallback() {
    @Override
    public void doFrame(long frameTimeNanos) {
        doAnimationFrame(getProvider().getFrameTime());
        // 如果全局的mAnimationCallbacks存在,则表示动画没有结束,需要继续post该FrameCallback到Choreographer中
        // 下一个Vsync信号过来时,仍然会回调该方法
        if (mAnimationCallbacks.size() > 0) {
            getProvider().postFrameCallback(this);
        }
    }
};
  
  
private void doAnimationFrame(long frameTime) {
    // 传进来的frameTime是从Choregrapher中获取的当前帧开始时间
    // 这里获取代码走到这里时的时间
    // 如果在这一帧中,有Input回调发生,那么这两个时间差就是处理Input回调的耗时
    // (Choreographer先处理Input回调,再处理Animation回调)
    long currentTime = SystemClock.uptimeMillis();
    final int size = mAnimationCallbacks.size();
    for (int i = 0; i < size; i++) {
        final AnimationFrameCallback callback = mAnimationCallbacks.get(i);
        if (callback == null) {
            continue;
        }
        if (isCallbackDue(callback, currentTime)) {
            // 这个callback从全局的mAnimationCallbacks中取的
            // 从前面的分析中可以得知,这个是ValueAnimator的实例
            callback.doAnimationFrame(frameTime);
            if (mCommitCallbacks.contains(callback)) {
                getProvider().postCommitCallback(new Runnable() {
                    @Override
                    public void run() {
                        commitAnimationFrame(callback, getProvider().getFrameTime());
                    }
                });
            }
        }
    }
    cleanUpList();
}

        最终会回调到ValueAnimator#doAnimationFrame方法中(ValueAnimator.java):

public final boolean doAnimationFrame(long frameTime) {
    // frameTime表示当前帧开始的时间
    if (mStartTime < 0) {
        // First frame. If there is start delay, start delay count down will happen *after* this
        // frame.
        // 设置动画的mStartTime,在Choreographer驱动的第一帧时会赋值,正常情况就会赋值为帧时间
        mStartTime = mReversing ? frameTime : frameTime + (long) (mStartDelay * sDurationScale);
    }
  
    // Handle pause/resume
    if (mPaused) {
        mPauseTime = frameTime;
        removeAnimationCallback();
        return false;
    } else if (mResumed) {
        mResumed = false;
        if (mPauseTime > 0) {
            // Offset by the duration that the animation was paused
            mStartTime += (frameTime - mPauseTime);
        }
    }
  
    // 在动画的启动过程中,如果没有设置延时启动时间或者初始动画进度,则会调用startAnimation方法
    // mRunning会在该方法中设置为true
    if (!mRunning) {
        // If not running, that means the animation is in the start delay phase of a forward
        // running animation. In the case of reversing, we want to run start delay in the end.
        if (mStartTime > frameTime && mSeekFraction == -1) {
            // This is when no seek fraction is set during start delay. If developers change the
            // seek fraction during the delay, animation will start from the seeked position
            // right away.
            return false;
        } else {
            // 如果设置了延时启动时间或者动画初始进度,则在此处认为动画开始
            // If mRunning is not set by now, that means non-zero start delay,
            // no seeking, not reversing. At this point, start delay has passed.
            mRunning = true;
            startAnimation();
        }
    }
  
    if (mLastFrameTime < 0) {
        if (mSeekFraction >= 0) {
            long seekTime = (long) (getScaledDuration() * mSeekFraction);
            mStartTime = frameTime - seekTime;
            mSeekFraction = -1;
        }
        mStartTimeCommitted = false; // allow start time to be compensated for jank
    }
    mLastFrameTime = frameTime;
    // The frame time might be before the start time during the first frame of
    // an animation.  The "current time" must always be on or after the start
    // time to avoid animating frames at negative time intervals.  In practice, this
    // is very rare and only happens when seeking backwards.
    final long currentTime = Math.max(frameTime, mStartTime);
    boolean finished = animateBasedOnTime(currentTime);
  
    if (finished) {
        // 动画结束
        endAnimation();
    }
    return finished;
}
  
  
// 接下来会调用到animateBasedOnTime方法
boolean animateBasedOnTime(long currentTime) {
    boolean done = false;
    if (mRunning) {
        // getScaledDuration是在开发者选项中设置的“动画程序时长”的系数,默认为1
        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;
        // 计算一些动画重复播放时的相关参数
        final boolean lastIterationFinished = (fraction >= mRepeatCount + 1) &&
                (mRepeatCount != INFINITE);
        if (scaledDuration == 0) {
            // 0 duration animator, ignore the repeat count and skip to the end
            done = true;
        } else if (newIteration && !lastIterationFinished) {
            // Time to repeat
            if (mListeners != null) {
                int numListeners = mListeners.size();
                for (int i = 0; i < numListeners; ++i) {
                    // 回调,通知客户端动画正在重复
                    mListeners.get(i).onAnimationRepeat(this);
                }
            }
        } else if (lastIterationFinished) {
            done = true;
        }
        mOverallFraction = clampFraction(fraction);
        float currentIterationFraction = getCurrentIterationFraction(
                mOverallFraction, mReversing);
        animateValue(currentIterationFraction);
    }
    return done;
}
  
  
// 这个方法上面也分析过,主要用来计算当前帧的属性取值
@CallSuper
void animateValue(float fraction) {
    // 根据插值器,把时间进度转化为动画进度
    fraction = mInterpolator.getInterpolation(fraction);
    mCurrentFraction = fraction;
    int numValues = mValues.length;
    for (int i = 0; i < numValues; ++i) {
        // 遍历所有属性,根据动画进度计算其属性取值
        mValues[i].calculateValue(fraction);
    }
    if (mUpdateListeners != null) {
        int numListeners = mUpdateListeners.size();
        for (int i = 0; i < numListeners; ++i) {
            // 回调onAnimationUpdate方法
            mUpdateListeners.get(i).onAnimationUpdate(this);
        }
    }
}
        根据上面的代码逻辑,可以看到其调用过程为:
        doAnimationFrame → animateBasedOnTime → animateValue → calculateValue

        下面分析一下计算当前帧属性取值的逻辑——calculateValue(PropertyValuesHolder.java):

void calculateValue(float fraction) {
    // 如前文分析,mKeyframes用来管理属性取值,这里的fraction是计算后的动画进度
    Object value = mKeyframes.getValue(fraction);
    // mAnimatedValue就是对应属性在某一帧的取值
    // ValueAnimate#getAnimatedValue方法获取的就是对应属性的这个成员
    mAnimatedValue = mConverter == null ? value : mConverter.convert(value);
}

        下面是计算某一帧属性取值的逻辑,前文中举例创建的属性为int型,所以此处的mKeyframes是IntKeyframeSet类型(IntKeyframeSet.java):

@Override
public Object getValue(float fraction) {
    return getIntValue(fraction);
}
  
  
@Override
public int getIntValue(float fraction) {
    if (fraction <= 0f) {
        // 动画首帧,首先取出前两个关键帧
        final IntKeyframe prevKeyframe = (IntKeyframe) mKeyframes.get(0);
        final IntKeyframe nextKeyframe = (IntKeyframe) mKeyframes.get(1);
        // 取出前两个关键帧的属性取值
        int prevValue = prevKeyframe.getIntValue();
        int nextValue = nextKeyframe.getIntValue();
        // 取出这两个关键帧的动画进度
        float prevFraction = prevKeyframe.getFraction();
        float nextFraction = nextKeyframe.getFraction();
        // 第二个关键帧是否设置了插值器,注意这个和ValueAnimator的插值器是两回事,Keyframe可以单独设置插值器
        final TimeInterpolator interpolator = nextKeyframe.getInterpolator();
        if (interpolator != null) {
            // 根据插值器转换动画进度
            fraction = interpolator.getInterpolation(fraction);
        }
        // 这里是再次换算出一个比例:在两个关键帧所对应的动画进度中,当前动画进度执行了多少
        float intervalFraction = (fraction - prevFraction) / (nextFraction - prevFraction);
        // 最后,把上面计算出的比例放到类型估值器中,最终得到当前动画进度的属性取值为多少
        return mEvaluator == null ?
                prevValue + (int)(intervalFraction * (nextValue - prevValue)) :
                ((Number)mEvaluator.evaluate(intervalFraction, prevValue, nextValue)).
                        intValue();
    } else if (fraction >= 1f) {
        // 动画尾帧,算法同上
        final IntKeyframe prevKeyframe = (IntKeyframe) mKeyframes.get(mNumKeyframes - 2);
        final IntKeyframe nextKeyframe = (IntKeyframe) mKeyframes.get(mNumKeyframes - 1);
        int prevValue = prevKeyframe.getIntValue();
        int nextValue = nextKeyframe.getIntValue();
        float prevFraction = prevKeyframe.getFraction();
        float nextFraction = nextKeyframe.getFraction();
        final TimeInterpolator interpolator = nextKeyframe.getInterpolator();
        if (interpolator != null) {
            fraction = interpolator.getInterpolation(fraction);
        }
        float intervalFraction = (fraction - prevFraction) / (nextFraction - prevFraction);
        return mEvaluator == null ?
                prevValue + (int)(intervalFraction * (nextValue - prevValue)) :
                ((Number)mEvaluator.evaluate(intervalFraction, prevValue, nextValue)).intValue();
    }
    IntKeyframe prevKeyframe = (IntKeyframe) mKeyframes.get(0);
    // 动画执行到中间,则遍历所有关键帧,找到当前动画进度临近的关键帧(第一个大于当前动画进度的关键帧)
    for (int i = 1; i < mNumKeyframes; ++i) {
        IntKeyframe nextKeyframe = (IntKeyframe) mKeyframes.get(i);
        if (fraction < nextKeyframe.getFraction()) {
            // 算法依然一样,不过是基于第一个关键帧和临近当前动画进度的关键帧计算的
            final TimeInterpolator interpolator = nextKeyframe.getInterpolator();
            float intervalFraction = (fraction - prevKeyframe.getFraction()) /
                (nextKeyframe.getFraction() - prevKeyframe.getFraction());
            int prevValue = prevKeyframe.getIntValue();
            int nextValue = nextKeyframe.getIntValue();
            // Apply interpolator on the proportional duration.
            if (interpolator != null) {
                intervalFraction = interpolator.getInterpolation(intervalFraction);
            }
            return mEvaluator == null ?
                    prevValue + (int)(intervalFraction * (nextValue - prevValue)) :
                    ((Number)mEvaluator.evaluate(intervalFraction, prevValue, nextValue)).
                            intValue();
        }
        prevKeyframe = nextKeyframe;
    }
    // shouldn't get here
    return ((Number)mKeyframes.get(mNumKeyframes - 1).getValue()).intValue();
}
        几种类型的Keyframe计算当前属性取值的算法都一样,可以抽象出以下几个步骤:
        (1) 以第一个关键帧为prevKeyframe
        (2) 遍历所有关键帧,找到第一个关键帧所对应的动画进度大于当前动画进度的关键帧,作为nextKeyframe
        (3) 计算当前动画进度在这两个关键帧内执行的百分比:
        (fraction - prevKeyframe.getFraction()) / (nextKeyframe.getFraction() - prevKeyframe.getFraction())
        (4) 检查nextKeyframe是否设置了插值器,如果设置了,则把第3步得到的百分比丢到插值器中换算
        (5) 如果设置了类型估值器,则把第4步得到百分比丢进去,得到当前动画进度时,该属性的取值
        至此,这一帧的属性取值都计算完毕,最终需要通知客户端进程的回调:
        ValueAnimator.AnimatorUpdateListener#onAnimationUpdate,客户端可以在该回调中获取该帧的动画进度以及对应属性的取值。
4.动画的结束

        根据上面代码分析,属性动画通过ValueAnimator#endAnimation方法结束:(ValueAnimator.java)

private void endAnimation() {
    if (mAnimationEndRequested) {
        return;
    }
    // 做一些收尾工作
    removeAnimationCallback();
  
    mAnimationEndRequested = true;
    mPaused = false;
    boolean notify = (mStarted || mRunning) && mListeners != null;
    if (notify && !mRunning) {
        // If it's not yet running, then start listeners weren't called. Call them now.
        notifyStartListeners();
    }
    mRunning = false;
    mStarted = false;
    mStartListenersCalled = false;
    mLastFrameTime = -1;
    mFirstFrameTime = -1;
    mStartTime = -1;
    if (notify && mListeners != null) {
        ArrayList<AnimatorListener> tmpListeners =
                (ArrayList<AnimatorListener>) mListeners.clone();
        int numListeners = tmpListeners.size();
        for (int i = 0; i < numListeners; ++i) {
            // 通知客户端动画结束
            tmpListeners.get(i).onAnimationEnd(this, mReversing);
        }
    }
    // mReversing needs to be reset *after* notifying the listeners for the end callbacks.
    mReversing = false;
    if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
        Trace.asyncTraceEnd(Trace.TRACE_TAG_VIEW, getNameForTrace(),
                System.identityHashCode(this));
    }
}
  
  
private void removeAnimationCallback() {
    if (!mSelfPulse) {
        return;
    }
    // 通知AnimationHandler动画结束
    getAnimationHandler().removeCallback(this);
}

        (AnimationHandler.java)

public void removeCallback(AnimationFrameCallback callback) {
    mCommitCallbacks.remove(callback);
    mDelayedCallbackStartTime.remove(callback);
    // 清除mAnimaationCallbacks列表里面对应的callback
    int id = mAnimationCallbacks.indexOf(callback);
    if (id >= 0) {
        mAnimationCallbacks.set(id, null);
        mListDirty = true;
    }
}
        至此,属性动画的过程就执行完毕。
        后续将会对属性动画的常见掉帧问题以及ObjectAnimator的机制进行分析。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值