什么樣的終點才能配的起這樣的經歷
前言
val valueAnimator = ValueAnimator.ofInt(0, 100,300).apply {
duration = 1000
interpolator = LinearInterpolator()
addUpdateListener {
}
}
valueAnimator.start()
問題:
- 0-100 中經過的時間到底是0.5s還是0.33s?
- ValueAnimator.ofInt(0, 300)有區別嗎?
原始碼決議
那我們先從ValueAnimator.ofInt()開始分析
//ValueAnimator
public static ValueAnimator ofInt(int... values) {
ValueAnimator anim = new ValueAnimator();
anim.setIntValues(values);
return anim;
}
我們重點是要查看values的去向,進入anim.setFloatValues(value)方法
//ValueAnimator
public void setIntValues(int... values) {
if (values == null || values.length == 0) {
return;
}
if (mValues == null || mValues.length == 0) {
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;
}
mValues為null,執行setValues(PropertyValuesHolder.ofInt("", values));最終呼叫了IntPropertyValuesHolder的構造方法
//IntPropertyValuesHolder
public IntPropertyValuesHolder(String propertyName, int... values) {
super(propertyName);
setIntValues(values);
}
//IntPropertyValuesHolder
public void setIntValues(int... values) {
mValueType = int.class;
mKeyframes = KeyframeSet.ofInt(values);
}
這里會生成KeyframeSet
//KeyframeSet
public static KeyframeSet ofInt(int... values) {
int numKeyframes = values.length;
IntKeyframe keyframes[] = new IntKeyframe[Math.max(numKeyframes,2)];
if (numKeyframes == 1) {
keyframes[0] = (IntKeyframe) Keyframe.ofInt(0f);
keyframes[1] = (IntKeyframe) Keyframe.ofInt(1f, values[0]);
} else {
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);
}
這里說明了我們在可變引數傳一個的時候為什么影片是從0開始,原來在內部在初始化的時候進行了處理,初始化操作就到這里,
public static Keyframe ofInt(float fraction, int value) {
return new IntKeyframe(fraction, value);
}
/**
* Internal subclass used when the keyframe value is of type int.
*/
static class IntKeyframe extends Keyframe {
/**
* The value of the animation at the time mFraction.
*/
int mValue;
IntKeyframe(float fraction, int value) {
mFraction = fraction;
mValue = value;
mValueType = int.class;
mHasValue = true;
}
}
在創建每個關鍵幀時,傳入了兩個引數,第一個引數就是表示這個關鍵幀在整個區域之間的位置,第二引數就是它表示的值是多少,看上面的代碼, i 表示的是第幾幀,numKeyframes 表示的是關鍵幀的總數量,所以 i/(numKeyframes - 1) 也就是表示這一系列關鍵幀是按等比例來分配的,
到這里對我們前面提出的問題,還是沒有解決?
只知道
0是第一幀(fraction = 0)
100是第二幀(fraction = 1/2)
300是第三幀(fraction = 1 )
//IntKeyframeSet
public IntKeyframeSet(IntKeyframe... keyframes) {
super(keyframes);
}
public class KeyframeSet implements Keyframes {
int mNumKeyframes;
Keyframe mFirstKeyframe;
Keyframe mLastKeyframe;
TimeInterpolator mInterpolator; // only used in the 2-keyframe case
List<Keyframe> mKeyframes; // only used when there are not 2 keyframes
TypeEvaluator mEvaluator;
public KeyframeSet(Keyframe... keyframes) {
mNumKeyframes = keyframes.length;
// immutable list
mKeyframes = Arrays.asList(keyframes);
mFirstKeyframe = keyframes[0];
mLastKeyframe = keyframes[mNumKeyframes - 1];
mInterpolator = mLastKeyframe.getInterpolator();
}
總結一下
ValueAnimator.ofInt流程圖

現在ValueAnimator#mValues持有PropertyValuesHolder
PropertyValuesHolder#mKeyframes持有IntKeyframeSet
IntKeyframeSet#mKeyframes持有IntKeyframe
屬性影片中什么時候用到了PropertyValuesHolder#mValues
那現在我們從入手valueAnimator.start(),看看什么時候用到了mValues
ValueAnimator.start()原始碼決議
只能從animator.start()中去尋找答案了
** ps:本次分析的是ValueAnimator,不是ObjectAnimator**
@Override
public void start() {
start(false);
}
private void start(boolean playBackwards) {
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;
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.
setCurrentPlayTime(0);
} else {
setCurrentFraction(mSeekFraction);
}
}
}
呼叫了內部的 start(boolean) 方法,前面無外乎就是一些變數的初始化,然后好像呼叫了很多方法,我們知道AnimationHandler是統一處理屬性影片的,那么就找和AnimationHandler相關的邏輯,
ok找到了,在addAnimationCallback(0)中,我們找到了和AnimationHandler相關的邏輯,來看看:
private void addAnimationCallback(long delay) {
if (!mSelfPulse) {
return;
}
getAnimationHandler().addAnimationFrameCallback(this, delay);
}
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));
}
}
//ValueAnimator
private AnimationFrameCallbackProvider getProvider() {
if (mProvider == null) {
mProvider = new MyFrameCallbackProvider();
}
return mProvider;
}
//MyFrameCallbackProvider
public void postFrameCallback(Choreographer.FrameCallback callback) {
mChoreographer.postFrameCallback(callback);
}
看到 Choreographer 了,感覺我們好像已經快接近真相了,不繼續跟下去了,
大概說一下后面的流程:
向底層注冊螢屏監聽信號:
Choreographer 內部有幾個佇列,通過postCallbackDelayedInternal(int callbackType,
Object action, Object token, long delayMillis) 方法將FrameCallback放進佇列中,callbackType是區分佇列型別的,屬性影片中傳入的是CALLBACK_ANIMATION用于區分這些佇列,接著,Choreographer#scheduleFrameLocked()->scheduleFrameLocked#()scheduleVsyncLocked()->FrameDisplayEventReceiver#scheduleVsyncLocked()->最終呼叫native的方法nativeScheduleVsync()
向底層注冊螢屏監聽信號,
當螢屏重繪信號來臨時:
nativeScheduleVsync()會向SurfaceFlinger注冊Vsync信號的監聽,VSync信號由SurfaceFlinger實作并定時發送,當Vsync信號來的時候就會回呼FrameDisplayEventReceiver#onVsync(),這個方法給發送一個帶時間戳Runnable訊息,這個Runnable訊息的run()實作就是FrameDisplayEventReceiver# run(), 接著就會執行doFrame()
具體的流程比如:nativeScheduleVsync怎么向SurfaceFlinger注冊監聽信號,以及SurfaceFlinger和VSync是什么?以后再去研究,
根據目前的資訊,我們先小結一下:
當 ValueAnimator 呼叫了 start() 方法之后,首先會對一些變數進行初始化作業并通知影片開始了,然后 ValueAnimator 實作了 AnimationFrameCallback 介面,并通過 AnimationHander 將自身 this 作為引數傳到 mAnimationCallbacks 串列里快取起來,而 AnimationHandler 在 mAnimationCallbacks 串列大小為 0 時會通過內部類 MyFrameCallbackProvider 將一個 mFrameCallback 作業快取到 Choreographer 的待執行佇列里,并向底層注冊監聽下一個螢屏重繪信號事件,
當螢屏重繪信號到的時候,Choreographer 的 doFrame() 會去將這些待執行佇列里的作業取出來執行,那么此時也就回呼了 AnimationHandler 的 mFrameCallback 作業,
那么,接下去就繼續看看,當接收到螢屏重繪信號之后,mFrameCallback 又繼續做了什么:
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() 來看看,屬性影片是怎么執行的
//AnimationHandler
private void doAnimationFrame(long frameTime) {
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.doAnimationFrame(frameTime);
if (mCommitCallbacks.contains(callback)) {
getProvider().postCommitCallback(new Runnable() {
@Override
public void run() {
commitAnimationFrame(callback, getProvider().getFrameTime());
}
});
}
}
}
cleanUpList();
}
- 是去回圈遍歷串列,取出每一個 ValueAnimator,就會去呼叫 ValueAnimator 的 doAnimationFrame();
- 是呼叫了 cleanUpList() 方法,處理掉已經結束的影片;
//ValueAnimator
public final boolean doAnimationFrame(long frameTime) {
if (mStartTime < 0) {
// First frame. If there is start delay, start delay count down will happen *after* this
// frame.
mStartTime = mReversing
? frameTime
: frameTime + (long) (mStartDelay * resolveDurationScale());
}
// 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);
}
}
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;
}
稍微概括一下,這個方法內部其實就做了三件事:
- 是處理第一幀影片的一些作業;
- 是根據當前時間計算當前幀的影片進度,所以影片的核心應該就是在 animateBaseOnTime() 這個方法里(剛開始問題的答案,或許也在這里面;
- 是判斷影片是否已經結束了,結束了就去呼叫 endAnimation();
我們重點在第二件事中查找答案:
//ValueAnimator
boolean animateBasedOnTime(long currentTime) {
boolean done = false;
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;
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;
}
- 根據當前時間以及影片第一幀時間還有影片持續的時長來計算當前的影片進度,
- 確保這個影片進度的取值在 0-1 之間,這里呼叫了兩個方法來輔助計算,我們就不跟進去了,之所以有這么多的輔助計算,那是因為,屬性影片支持 setRepeatCount() 來設定影片的回圈次數,而從始至終的影片第一幀的時間都是 mStrtTime 一個值,所以在第一個步驟中根據當前時間計算影片進度時會發現進度值是可能會超過 1 的,比如 1.5, 2.5, 3.5 等等,所以第二個步驟的輔助計算,就是將這些值等價換算到 0-1 之間,
我們首先fraction 是怎么計算的
final float fraction = scaledDuration > 0 ?
(float)(currentTime - mStartTime) / scaledDuration : 1f;
每個影片在處理當前幀的影片邏輯時,首先會先根據當前時間和影片第一幀時間以及影片的持續時長來初步計算出當前幀時影片所處的進度,
//ValueAnimator
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);
}
}
}
mUpdateListeners.get(i).onAnimationUpdate(this); 這是不是我們剛開始給ValueAnimator設定的addUpdateListener,引數也是ValueAnimator,也就是在這個函式中,計算了當前屬性影片所屬于的區間,和經過的時長,然后通知影片的進度回呼
終于找到了mValues是在這里使用計算的
這部分作業主要就是呼叫了 mValues[i].calculateValue(fraction) 這一行代碼來實作,mValues 是一個 PropertyValuesHolder 型別的陣列,所以關鍵就是去看看這個類的 calculateValue() 做了啥:
//PropertyValuesHolder
@Override
void calculateValue(float fraction) {
mIntAnimatedValue = mIntKeyframes.getIntValue(fraction);
}
我們在使用 ValueAnimator 時,注冊了影片進度回呼,然后在回呼里取當前的值時其實也就是取到上面那個 mAnimatedValue 變數的值,而這個變數的值是通過 mKeyframes.getValue() 計算出來的,那么再繼續跟進看看:
//KeyframeSet
@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();
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();
}
ok,在這里就一步一步的取到了我們剛開始看到的值,
當關鍵幀超過兩幀時,分三種情況來處理:
- 第一幀的處理;
- 中間幀的處理;
- 最后一幀的處理;
分開來看一下吧,首先是第一幀的處理邏輯:
@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();
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();
}
return ((Number)mKeyframes.get(mNumKeyframes - 1).getValue()).intValue();
}
mEvaluator我們沒有設定,為null,走的是 prevValue + (int)(intervalFraction * (nextValue - prevValue)) ;
intervalFraction = (fraction - prevFraction) / (nextFraction - prevFraction); 表示,在這個幀區間內,所占的比例,
因為是第一幀的處理邏輯:
intervalFraction = (fraction - prevFraction) / (nextFraction - prevFraction);
intervalFraction = 0
第一幀是 prevValue,也就是我們剛開始設定的值(ofInt(0,100,300))中的0,
@Override
public int getIntValue(float fraction) {
/*
* 中間幀的處理
*
*/
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();
}
處理中間幀的邏輯:關鍵找出當前進度處于哪兩個關鍵幀之間:
從第一幀開始,按順序遍歷每一幀,然后去判斷當前的影片進度跟這一幀保存的位置資訊來找出當前進度是否就是落在某兩個關鍵幀之間,
至此,我們已經將整個流程梳理出來了,兩部分小結的內容整合起來就是這次梳理出來的整個屬性影片從 start() 之后,到我們在 onAnimationUpdate 回呼中取到我們需要的值,
OK~~~~~
結束了
回答剛開始提出的問題
- 0-100 中經過的時間到底是0.5s還是0.33s?
answer:100對應的幀為1/2, fraction = scaledDuration > 0 ? (float)(currentTime - mStartTime) / scaledDuration : 1f;
假設剛開始記錄的時間為0
1/2 = (currentTime -0)/1s
currentTime = 0.5s - ValueAnimator.ofInt(0, 300)有區別嗎?
answer:是有區別的,影片屬性值為100時,經過的時間為0.33s(不要問怎么算出來的)
ValueAnimator時序圖結尾:

ps:本篇分析的原始碼基于 android-29 版本
轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/278109.html
標籤:其他
