属性动画源码分析

前几天与小伙伴交流,他做Android 时间不长,做了很多View了,觉得View已经全部掌握了。然后我以一个过来人的身份(臭不要脸)问他属性动画的原理,然后他支支吾吾的说就是通过反射设置的啊…

自己曾经也看过属性动画的源码,但是没好好的整理一份,这次趁这个机会还是整理一番。
首先属性动画中是通过一直调用相应的方法来改变View中相应的值。如下面的代码,使用ValueAnimation(使用Handler循环发消息可能更好理解)来构造一个动画在onAnimationUpdate中手动去改变改变Alpha 和TranslationX 的值也是可以实现动画的。所以得出来的结论是属性动画的重点不是通过反射改变值了(管它是通过什么呢),而是如何去控制设置动画的时机以及如何去控制绘制的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public void animation() {
PropertyValuesHolder propertyValuesHolder3 =
PropertyValuesHolder.ofFloat("alpha", 1.0f, 0.1f, 0.5f);
PropertyValuesHolder propertyValuesHolder4 =
PropertyValuesHolder.ofFloat("translationX", 0.0f, 100.0f, 50.0f);
ValueAnimator valueAnimator =
ValueAnimator.ofPropertyValuesHolder(propertyValuesHolder3, propertyValuesHolder4);
valueAnimator.setDuration(10000);
valueAnimator.setStartDelay(5000);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override public void onAnimationUpdate(ValueAnimator animation) {
float alpha = (Float) (animation.getAnimatedValue("alpha"));
float translationX = (Float) (animation.getAnimatedValue("translationX"));
findViewById(R.id.anim_image4).setAlpha(alpha);
findViewById(R.id.anim_image4).setTranslationX(translationX);
Log.e(TAG, "alpha " + alpha + ", translationX " + translationX);
}
});
valueAnimator.start();
}

源码分析

就用上面的例子来分析源码,不过只看TranslationX 一个属性了(多个属性最终其实是一样的)。所以代码先可以缩减下。

1
2
3
4
5
6
PropertyValuesHolder propertyValuesHolder3 =
PropertyValuesHolder.ofFloat("alpha", 1.0f, 0.1f, 0.5f);
ValueAnimator valueAnimator =
ValueAnimator.ofPropertyValuesHolder(propertyValuesHolder3);
valueAnimator.setDuration(10000);
valueAnimator.start();

代码的作用分成了两部分,ValueAnimator的构造和ValueAnimator#start,所以看源代码也从这两个方面入手。

ValueAnimator 构造过程

创建PropertyValuesHolder对象
1
2
3
public static PropertyValuesHolder ofFloat(Property<?, Float> property, float... values) {
return new FloatPropertyValuesHolder(property, values);
}

直接new 一个FloatPropertyValuesHolder对象(这里根据需求可能是IntPropertyValuesHolder之类的)

1
2
3
4
public FloatPropertyValuesHolder(String propertyName, float... values) {
super(propertyName);
setFloatValues(values);
}

FloatPropertyValuesHolder构造函数里面第1行是直接复制语句,关键是下面的setFloatValues

1
2
3
4
5
// FloatPropertyValuesHolder#setFloatValues
public void setFloatValues(float... values) {
super.setFloatValues(values);
mFloatKeyframes = (Keyframes.FloatKeyframes) mKeyframes;
}

关键是调用基类的setFloatValues

1
2
3
4
5
//PropertyValuesHolder#setFloatValues
public void setFloatValues(float... values) {
mValueType = float.class;
mKeyframes = KeyframeSet.ofFloat(values);
}

主要是构造了一个Keyframes对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
//KeyframeSet#ofFloat
public static KeyframeSet ofFloat(float... values) {
boolean badValue = false;
int numKeyframes = values.length;
FloatKeyframe keyframes[] = new FloatKeyframe[Math.max(numKeyframes,2)];
if (numKeyframes == 1) {
keyframes[0] = (FloatKeyframe) Keyframe.ofFloat(0f);
keyframes[1] = (FloatKeyframe) Keyframe.ofFloat(1f, values[0]);
if (Float.isNaN(values[0])) {
badValue = true;
}
} else {
keyframes[0] = (FloatKeyframe) Keyframe.ofFloat(0f, values[0]);
for (int i = 1; i < numKeyframes; ++i) {
keyframes[i] =
(FloatKeyframe) Keyframe.ofFloat((float) i / (numKeyframes - 1), values[i]);
if (Float.isNaN(values[i])) {
badValue = true;
}
}
}
if (badValue) {
Log.w("Animator", "Bad value (NaN) in float animator");
}
return new FloatKeyframeSet(keyframes);
}

Keyframe中保存的是动画中某一帧的开始时间fraction(0.0f-1.0f之间的值)和相应的值,相当于保存了动画的运动轨迹,比如最上面的ofFloat("translationX", 0.0f, 100.0f, 50.0f),动画会保存轨迹是0.0f到100.0f最后到50.0f,而不是直接0.0f到50.0f。

第6行创建了FloatKeyframe的数组,分了两种情况,numKeyframes == 1时只会设置keyframes[0]keyframes[1]分别代表的是动画的起始位置和结束位置;numKeyframes > 1时,则将动画均分成numKeyframes - 1份,分别保存着起始时间和值的信息。最后就是构建了一个FloatKeyframeSet对象,最后里面全是赋值的逻辑,代码就不再贴出来。

总结:到此一个PropertyValuesHolder对象就创建出来了,主要做的事情就是记录了一组数据,记录着动画的轨迹信息。

创建ValueAnimator
1
2
3
4
5
public static ValueAnimator ofPropertyValuesHolder(PropertyValuesHolder... values) {
ValueAnimator anim = new ValueAnimator();
anim.setValues(values);
return anim;
}

首先创建ValueAnimator对象,然后将步骤1中的值设置进去。

1
2
3
4
5
6
7
8
9
10
11
public void setValues(PropertyValuesHolder... values) {
int numValues = values.length;
mValues = values;
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;
}

将步骤1中的信息保存在ValueAnimator的mValues 和mValuesMap 中。

总结:到此动画已经构造完成,并且相应的信息保存在ValueAnimator中。

执行动画

ValueAnimator#start(),直接调用了ValueAnimator#start(false)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
private void start(boolean playBackwards) {
if (Looper.myLooper() == null) {
throw new AndroidRuntimeException("Animators may only be run on Looper threads");
}
mReversing = playBackwards;
// 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 = 0;
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.
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.
setCurrentPlayTime(0);
} else {
setCurrentFraction(mSeekFraction);
}
}
}

AnimationHandler直接看注视是通过Choreographer来实现定时回调的。Choreographer可能讲的不是很清楚,简单理解每当绘制一帧的时候Choreographer都会有一个回调执行。

直接看到24行创建了一个AnimationHandler然后执行了addAnimationFrameCallbackmStartDelay == 0所以还会执行startAnimationmSeekFraction == -1所以还会执行setCurrentPlayTime(0);

addAnimationFrameCallback执行的过程
1
2
3
4
5
6
7
8
9
10
11
12
13
//AnimationHandler#addAnimationFrameCallback
public void addAnimationFrameCallback(final AnimationFrameCallback callback, long delay) {
if (mAnimationCallbacks.size() == 0) {
getProvider().postFrameCallback(mFrameCallback);
}
if (!mAnimationCallbacks.contains(callback)) {
mAnimationCallbacks.add(callback);
}
if (delay > 0) {
mDelayedCallbackStartTime.put(callback, (SystemClock.uptimeMillis() + delay));
}
}

最开始mAnimationCallbacks.size() == 0所以会执行getProvider().postFrameCallback(mFrameCallback)mAnimationCallbacks.add(callback)

1
2
3
4
5
6
7
8
//AnimationHandler.MyFrameCallbackProvider#postFrameCallback
private class MyFrameCallbackProvider implements AnimationFrameCallbackProvider {
final Choreographer mChoreographer = Choreographer.getInstance();
@Override
public void postFrameCallback(Choreographer.FrameCallback callback) {
mChoreographer.postFrameCallback(callback);
}
}

第6行执行到了Choreographer的方法,继续跟下去,中间跳过几个方法,直接到最终调用的地方。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//Choreographer#postCallbackDelayedInternal
private void postCallbackDelayedInternal(int callbackType,
Object action, Object token, long delayMillis) {
//省略部分代码
synchronized (mLock) {
final long now = SystemClock.uptimeMillis();
final long dueTime = now + delayMillis;
mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token);
if (dueTime <= now) {
scheduleFrameLocked(now);
} else {
Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);
msg.arg1 = callbackType;
msg.setAsynchronous(true);
mHandler.sendMessageAtTime(msg, dueTime);
}
}
}

第10行的判断因为delayMillis == 0,所以直接会执行scheduleFrameLocked

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//Choreographer#scheduleFrameLocked
private void scheduleFrameLocked(long now) {
if (!mFrameScheduled) {
mFrameScheduled = true;
if (USE_VSYNC) {
//省略代码
// If running on the Looper thread, then schedule the vsync immediately,
// otherwise post a message to schedule the vsync from the UI thread
// as soon as possible.
if (isRunningOnLooperThreadLocked()) {
scheduleVsyncLocked();
} else {
Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_VSYNC);
msg.setAsynchronous(true);
mHandler.sendMessageAtFrontOfQueue(msg);
}
} else {
//省略代码
}
}
}

这里的调用会直接执行到第11行,最后会执行到nativeScheduleVsync(mReceiverPtr),是一个native 方法,在此打住,后面回调到Choreographer.FrameDisplayEventReceiver#onVsync

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
//Choreographer.FrameDisplayEventReceiver#onVsync
@Override
public void onVsync(long timestampNanos, int builtInDisplayId, int frame) {
//省略代码
long now = System.nanoTime();
if (timestampNanos > now) {
Log.w(TAG, "Frame time is " + ((timestampNanos - now) * 0.000001f)
+ " ms in the future! Check that graphics HAL is generating vsync "
+ "timestamps using the correct timebase.");
timestampNanos = now;
}
if (mHavePendingVsync) {
Log.w(TAG, "Already have a pending vsync event. There should only be "
+ "one at a time.");
} else {
mHavePendingVsync = true;
}
mTimestampNanos = timestampNanos;
mFrame = frame;
Message msg = Message.obtain(mHandler, this);
msg.setAsynchronous(true);
mHandler.sendMessageAtTime(msg, timestampNanos / TimeUtils.NANOS_PER_MS);
}

第24行代码表示将在timestampNanos / TimeUtils.NANOS_PER_MS时会执行Handler抛出的Message,最终会执行到Choreographer#doFrame

1
2
3
4
5
6
7
8
9
10
//Choreographer#doFrame
void doFrame(long frameTimeNanos, int frame) {
//省略代码
try {
//省略代码
doCallbacks(Choreographer.CALLBACK_INPUT, frameTimeNanos);
//省略代码
}
//省略代码
}

执行到doCallbacks

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
//Choreographer#doCallbacks
void doCallbacks(int callbackType, long frameTimeNanos) {
CallbackRecord callbacks;
synchronized (mLock) {
//省略代码
final long now = System.nanoTime();
callbacks = mCallbackQueues[callbackType].extractDueCallbacksLocked(
now / TimeUtils.NANOS_PER_MS);
if (callbacks == null) {
return;
}
//省略代码
}
try {
//省略代码
for (CallbackRecord c = callbacks; c != null; c = c.next) {
if (DEBUG_FRAMES) {
Log.d(TAG, "RunCallback: type=" + callbackType
+ ", action=" + c.action + ", token=" + c.token
+ ", latencyMillis=" + (SystemClock.uptimeMillis() - c.dueTime));
}
c.run(frameTimeNanos);
}
}
//省略代码
}

第7行得到的是callbackscallbackTypeCALLBACK_ANIMATION,执行第22行。

1
2
3
4
5
6
7
8
//Choreographer.CallbackRecord#run
public void run(long frameTimeNanos) {
if (token == FRAME_CALLBACK_TOKEN) {
((FrameCallback)action).doFrame(frameTimeNanos);
} else {
((Runnable)action).run();
}
}

最终最终会执行到4行。这个action 就是最开始postFrameCallback传入的,绕了一大段现在绕回来了。

总结:这个过程相当于设置了一个Choreographer的回调,然后调用VSync 相关方法,最后回调回来调用最初设置的Choreographer回调。
回到AnimationHandler查看回调的执行。

1
2
3
4
5
6
7
8
9
10
//AnimationHandler
private final Choreographer.FrameCallback mFrameCallback = new Choreographer.FrameCallback() {
@Override
public void doFrame(long frameTimeNanos) {
doAnimationFrame(getProvider().getFrameTime());
if (mAnimationCallbacks.size() > 0) {
getProvider().postFrameCallback(this);
}
}
};

执行doAnimationFrame,然后判断以后继续执行postFrameCallback,相当于逻辑再走一遍。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//AnimationHandler#doAnimationFrame
private void doAnimationFrame(long frameTime) {
int size = mAnimationCallbacks.size();
long currentTime = SystemClock.uptimeMillis();
for (int i = 0; i < size; i++) {
final AnimationFrameCallback callback = mAnimationCallbacks.get(i);
if (callback == null) {
continue;
}
if (isCallbackDue(callback, currentTime)) {
callback.doAnimationFrame(frameTime);
//省略代码
}
}
cleanUpList();
}

直接看第11行代码,回调到ValueAnimator#doAnimationFrame

1
2
3
4
5
6
7
8
9
10
//ValueAnimator#doAnimationFrame
public final void doAnimationFrame(long frameTime) {
//省略代码
final long currentTime = Math.max(frameTime, mStartTime);
boolean finished = animateBasedOnTime(currentTime);
if (finished) {
endAnimation();
}
}

第4行获取到的是绘制该帧的时间。然后执行animateBasedOnTime

1
2
3
4
5
6
7
8
9
//ValueAnimator#animateBasedOnTime
boolean animateBasedOnTime(long currentTime) {
boolean done = false;
if (mRunning) {
//省略代码
animateValue(currentIterationFraction);
}
return done;
}

计算出时间过去的百分比,然后执行到第6行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//ValueAnimator#animateValue
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) {
mUpdateListeners.get(i).onAnimationUpdate(this);
}
}
}

第3行通过Interpolator计算出当前动画完成的百分比,第7行通过动画执行的百分比计算相应属性的值修改最开始保存的值。第12行回调onAnimationUpdate。主要看calculateValue,流程是PropertyValuesHolder.FloatPropertyValuesHolder#getFloatValue,最后执行到FloatKeyframeSet.getFloatValue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//FloatKeyframeSet#getFloatValue
@Override
public float getFloatValue(float fraction) {
if (mNumKeyframes == 2) {
if (firstTime) {
firstTime = false;
firstValue = ((FloatKeyframe) mKeyframes.get(0)).getFloatValue();
lastValue = ((FloatKeyframe) mKeyframes.get(1)).getFloatValue();
deltaValue = lastValue - firstValue;
}
if (mInterpolator != null) {
fraction = mInterpolator.getInterpolation(fraction);
}
if (mEvaluator == null) {
return firstValue + fraction * deltaValue;
} else {
return ((Number)mEvaluator.evaluate(fraction, firstValue, lastValue)).floatValue();
}
}
//省略代码
}

为了简单只看mNumKeyframes == 2的情况,第5-10行计算出开始值、结束值以及属性的变化大小。第14行去判断有没有估值器,没有直接线性变化了,有则执行第17行,会使用Evaluator 和当前动画执行的百分比来计算属性的值。

总结:到此完整的从构造到通过VSync 回调,再到最后修改值的流程走了一遍。
什么,没有看到反射调用??这个可以去看看ObjectAnimator.animateValue,这里不展开了,嘿嘿。

startAnimation执行过程

主要是标志动画开始,初始化一些属性,比如默认的Evaluator

setCurrentPlayTime(0)执行过程

主要是用来设置动画的起始位置,这里的起始位置是0,所以不在展开了。

总结

所以属性动画的原理是什么呢?随着时间变化计算出动画完成的百分比,然后根据动画完成的百分比再计算出属性的最终值,最后设置属性,这个流程中还包含了通过Interpolator计算动画完成的百分比,通过Evaluator计算出最终值。内部还是比较复杂的,特别是通过Choreographer回调的时候,没有完整的分析。