一. 概述
属性动画是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的机制进行分析。