Android原始碼解析-動畫篇
Android在3.0版本中引入了新的動畫實現:屬性動畫。我們一般稱之為
Animator
。這種動畫通過變更控制元件屬性達到動畫效果。其中,屬性動畫最重要的一點,就是控制了動畫的時序,我們不妨來看下屬性動畫的簡單用法:
//code1
ValueAnimator animator = ValueAnimator.ofInt(0,100)//line1
.setDuration(100);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
Log.d("animator-demo","onAnimationUpdate "+valueAnimator.getAnimatedValue());
}
});
animator.start();
複製程式碼
code1
非常簡單,就是定義了這樣的ValueAnimator
物件: 1.物件的過渡區間設定為0~100
2.設定了動畫時間100
3.設定了一個AnimatorUpdateListener
監聽器 我們通過列印***animator-demo
***日誌可以得到:
11-08 01:21:00.594 29311 29311 D animator-demo: onAnimationUpdate 0
11-08 01:21:00.606 29311 29311 D animator-demo: onAnimationUpdate 0
11-08 01:21:00.626 29311 29311 D animator-demo: onAnimationUpdate 7
11-08 01:21:00.644 29311 29311 D animator-demo: onAnimationUpdate 30
11-08 01:21:00.664 29311 29311 D animator-demo: onAnimationUpdate 59
11-08 01:21:00.681 29311 29311 D animator-demo: onAnimationUpdate 84
11-08 01:21:00.701 29311 29311 D animator-demo: onAnimationUpdate 98
11-08 01:21:00.718 29311 29311 D animator-demo:
onAnimationUpdate 100
複製程式碼
可以看出,通過
ValueAnimator
我們很平滑的從0過渡到了100,而並不關心其中的時序和數值的對應關係。ValueAnimator
是整個屬性動畫的基礎,因此本章將重點分析ValueAnimator
的內部機制。我們先來看下ValueAnimator
的繼承關係圖:
ValueAnimator
繼承於Animator
,在Animator
中只是定義了一些常用介面和一些基礎Api
,比如:
AnimatorListener
回撥start()
,end()
函式等 此外,ValueAnimator
還實現了介面AnimationFrameCallback
。這個介面是在AnimationHandler
類中定義的回撥介面,這個介面,我們一會兒會提到,它跟我們的動畫息息相關。
有了以上的知識儲備,我們可以開始我們下一步,我們要使用
ValueAnimator
,就需要構造它,上面的code1例子中我們使用了ValueAnimator.ofInt(0,100)
的靜態工廠方式去構造一個int屬性集合的ValueAnimator
物件。一般情況下,你也可以通過new ValueAnimator()
的方式構建一個ValueAnimator
物件,然後通過set[Type]Values
方式注入你所需要的值集合。但是相比第一種方法,通過new
方式的構造手段程式碼偏多而且不集中,也不利於維護(當然凡事沒有絕對,需要考慮你自己的業務場景)。
public static ValueAnimator ofInt(int... values) {
ValueAnimator anim = new ValueAnimator();
anim.setIntValues(values);
return anim;
}
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;
}
複製程式碼
在
ofInt
函式中,ValueAnimator
將生成一個ValueAnimator
物件,然後通過呼叫setIntValues
方法將values
陣列轉為PropertyValuesHolder
物件。而int
陣列型別values
往PropertyValuesHolder
物件的轉換是通過PropertyValuesHolder
的ofInt
方法實現:
//code PropertyValuesHolder.java
public static PropertyValuesHolder ofInt(String propertyName, int... values) {
return new IntPropertyValuesHolder(propertyName, values);
}
複製程式碼
PropertyValuesHolder
是什麼呢?我們可以通過PropertyValuesHolder
的註釋看出一些端倪:
/**
* This class holds information about a property and the values that that property
* should take on during an animation. PropertyValuesHolder objects can be used to create
* animations with ValueAnimator or ObjectAnimator that operate on several different properties
* in parallel.
*/
複製程式碼
大致意思就是一個存放屬性和值的容器,而每次動畫的過程中都會從這個容器中取值或者設定值。
PropertyValuesHolder
的ofInt
方法將返回一個IntPropertyValuesHolder
型別物件。這個型別的作用就像它名字一樣限定了Holder
中所存放的型別是Int
型別。Ok,我們說到這裡,我們可以看到一個簡單ValueAnimator
物件的生成,實際上伴隨著多個型別的物件。
ValueAnimator
的setDuration
方法純粹就是記錄一個mDuration
時間,沒有特別的操作。
@Override
public ValueAnimator setDuration(long duration) {
if (duration < 0) {
throw new IllegalArgumentException("Animators cannot have negative duration: " +
duration);
}
mDuration = duration;
return this;
}
複製程式碼
我們重點看下
start()
方法:
//code ValueAnimator.java
@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;
....
mStarted = true;
mPaused = false;
mRunning = false;
mAnimationEndRequested = false;
mLastFrameTime = 0;
AnimationHandler animationHandler = AnimationHandler.getInstance();
animationHandler.addAnimationFrameCallback(this, (long) (mStartDelay * sDurationScale));
//加入到animationHandler管理
if (mStartDelay == 0 || mSeekFraction >= 0) {
startAnimation();
if (mSeekFraction == -1) {
setCurrentPlayTime(0);
} else {
setCurrentFraction(mSeekFraction);
}
}
}
複製程式碼
在
ValueAnimator
的start
函式中會呼叫內部的start(boolean playBackwards)
函式。引數playBackwards
代表動畫是否還有回彈操作。可以通過ValueAnimator.reverse()
方法將其設定為true
。
我們剛才說到,
ValueAnimator
實現了AnimationFrameCallback
介面,ValueAnimator.start(boolean)
程式碼中,ValueAnimator
先通過一個AnimationHandler.getInstance()
方法獲取執行緒內的AnimationHandler
單例,然後將實現了AnimationFrameCallback
的ValueAnimator
物件(也就是自己)通過addAnimationFrameCallback
方法加入到AnimationHandler
物件中去。
//AnimationHandler.java
//sAnimatorHandler是一個ThreadLocal變數,用於儲存AnimationHandler的執行緒單例
public static AnimationHandler getInstance() {
if (sAnimatorHandler.get() == null) {
sAnimatorHandler.set(new AnimationHandler());
}
return sAnimatorHandler.get();
}
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));
}
}
複製程式碼
在
AnimationHandler
類的addAnimationFrameCallback
方法中,AnimationHandler
將會往getProvider()
物件中post一個回撥物件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);//進行迴圈動畫
}
}
};
複製程式碼
mFrameCallback
是Choreographer.FrameCallback
的實現類,它實現了doFrame
回撥介面,在這個介面中,將通過呼叫doAnimationFrame
函式來執行mAnimationCallbacks
佇列中的動畫,最後當mAnimationCallbacks
佇列中還有物件的時候,將再次執行getProvider().postFrameCallback(this);
函式進行迴圈動畫操作。
getProvider()
返回一個AnimationFrameCallbackProvider
型別的物件。AnimationFrameCallbackProvider
是什麼呢?AnimationFrameCallbackProvider
其實就是一個跟最終的動畫操作物件**Choreographer
**互動的一個介面物件。而在AnimationHandler
類中,它的實現類是MyFrameCallbackProvider
。
//code AnimationHandler.java
private AnimationFrameCallbackProvider getProvider() {
if (mProvider == null) {
mProvider = new MyFrameCallbackProvider();
}
return mProvider;
}
複製程式碼
MyFrameCallbackProvider
內部實現了跟**Choreographer
**物件的操作:
private class MyFrameCallbackProvider implements AnimationFrameCallbackProvider {
final Choreographer mChoreographer = Choreographer.getInstance();
@Override
public void postFrameCallback(Choreographer.FrameCallback callback) {
mChoreographer.postFrameCallback(callback);
}
....
}
複製程式碼
Choreographer
物件是什麼呢?如果你做過動畫,或者深入研究過動畫相關,相信對這個類或者這個物件並不陌生,它是android系統中所有動畫和繪製的管理者。簡單概括起來,Choreographer
就是一個步調管理者,它是什麼步調呢?就是以16ms左右為頻率做組成的一個繪製訊號。這部分涉及到Android的繪製系統,我們不深入探究,我們可以寫個程式碼簡單瞭解一下:
long time = SystemClock.uptimeMillis();
Choreographer.getInstance().postFrameCallback(new Choreographer.FrameCallback() {
@Override
public void doFrame(long l) {
Log.d("Choreographer","doFrame use time= "+(SystemClock.uptimeMillis() - time)+"ms");
time = SystemClock.uptimeMillis();
Choreographer.getInstance().postFrameCallback(this);
}
});
複製程式碼
我們往
Choreographer
物件中post
一個FrameCallback
匿名物件,通過變數time來計算每次步調的時差。最後我們可以在日誌中輸出:
11-08 05:31:25.290 9080 9080 D Choreographer: doFrame use time= 18ms
11-08 05:31:25.308 9080 9080 D Choreographer: doFrame use time= 18ms
11-08 05:31:25.327 9080 9080 D Choreographer: doFrame use time= 19ms
11-08 05:31:25.345 9080 9080 D Choreographer: doFrame use time= 18ms
11-08 05:31:25.363 9080 9080 D Choreographer: doFrame use time= 19ms
11-08 05:31:25.383 9080 9080 D Choreographer: doFrame use time= 19ms
11-08 05:31:25.400 9080 9080 D Choreographer: doFrame use time= 18ms
11-08 05:31:25.421 9080 9080 D Choreographer: doFrame use time= 21ms
11-08 05:31:25.439 9080 9080 D Choreographer: doFrame use time= 18ms
11-08 05:31:25.457 9080 9080 D Choreographer: doFrame use time= 17ms
11-08 05:31:25.474 9080 9080 D Choreographer: doFrame use time= 18ms
11-08 05:31:25.494 9080 9080 D Choreographer: doFrame use time= 19ms
11-08 05:31:25.513 9080 9080 D Choreographer: doFrame use time= 19ms
複製程式碼
可以看出,
Choreographer
會在一定頻率的步調中執行繪製函式。而這種步調或者是用軟體模擬,或者是通過系統的 VSYNC 訊號實現。我們來整理一下ValueAnimator
的start
流程:
通過上面的流程圖我們可以看出,在
AnimationHandler
進行繪製的時候,實際上是呼叫了ValueAnimator
的doAnimationFrame
方法:
public final void doAnimationFrame(long frameTime) {
AnimationHandler handler = AnimationHandler.getInstance();
if (mLastFrameTime == 0) {
// First frame
handler.addOneShotCommitCallback(this);
//對於第一幀新增到commit回撥
if (mStartDelay > 0) {
startAnimation();
}
if (mSeekFraction < 0) {
mStartTime = frameTime;
} else {
long seekTime = (long) (getScaledDuration() * mSeekFraction);
mStartTime = frameTime - seekTime;
mSeekFraction = -1;
}
mStartTimeCommitted = false; }
mLastFrameTime = frameTime;
if (mPaused) {
mPauseTime = frameTime;
handler.removeCallback(this);
return;
} else if (mResumed) {
mResumed = false;
if (mPauseTime > 0) {
mStartTime += (frameTime - mPauseTime);
mStartTimeCommitted = false; // allow start time to be compensated for jank
}
handler.addOneShotCommitCallback(this);
}
final long currentTime = Math.max(frameTime, mStartTime);
boolean finished = animateBasedOnTime(currentTime);//動畫處理函式
if (finished) {
endAnimation();
}
}
複製程式碼
對於第一幀或者
resume
後的動畫,將通過handler
物件的addOneShotCommitCallback
方法將Callback
物件加入到Commit
佇列中去。之後將呼叫動畫處理函式:animateBasedOnTime(long)
。
boolean animateBasedOnTime(long currentTime) {
boolean done = false;
if (mRunning) {
....
animateValue(currentIterationFraction);
}
return done;
}
複製程式碼
animateBasedOnTime
函式將呼叫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);
}
}
}
複製程式碼
由於動畫的整個過程相當於是一個以時間為變數的函式:
x = f(t)
。(t代表時間) 為了方便計算,動畫的計算過程會先將時間變數歸一化,行程進度變數fraction
,然後通過差值計算得到相應的差值變數賦值給fraction
。 比如:你執行動畫400ms
,現在你執行到了200ms
,那麼你歸一化以後的進度變數就為200/400 = .5f
。如果你採用的是線性差值器的話那麼你的差值變數也同樣為.5f
。mValues
變數指的就是我們上面提到的PropertyValuesHolder
變數。PropertyValuesHolder
的calculateValue
函式,將呼叫mKeyframeSet
的getValue
函式,而這個函式的引數,就是我們上面
//PropertyValuesHolder.java
void calculateValue(float fraction) {
mAnimatedValue = mKeyframeSet.getValue(fraction);
}
複製程式碼
KeyframeSet是什麼呢?我們通過閱讀這個成員的註釋可以看出一些門道:
/**
* The set of keyframes (time/value pairs) that define this animation.
*/
KeyframeSet mKeyframeSet = null;
複製程式碼
簡要說明,就是儲存了一些value值,什麼樣的value值呢?用於計算時間和對應值vaue的value集合。我們不妨看下mKeyframeSet是在哪兒被賦值的。由於我們是通過"ValueAnimator.ofInt()"方式來生成一個
ValueAnimator
物件,因此,ValueAnimator
將會通過setIntValues
函式給屬性賦值:
public void setIntValues(int... values) {
mValueType = int.class;
mKeyframeSet = KeyframeSet.ofInt(values);
//靜態構造KeyframeSet
}
public static KeyframeSet ofInt(int... values//傳入的是[0,100]) {
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 { //step2
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,100]
,所以if
語句將跳轉到我們的step2
處,之後給每一個值都生成一個Keyframe
物件幀放入keyframes
陣列集合中,再將陣列集合keyframes
存入物件IntKeyframeSet
中。Keyframe
通過靜態方法ofInt
來構建一個Keyframe
物件,這個物件第一個浮點引數,代表你這個值在陣列中的偏移。比如你的陣列是[0,1,2,3,4]
,那麼2在此陣列中的偏移為2 / (5 -1) = 50%
。我們再回到PropertyValuesHolder
的calculateValue
方法,此方法裡呼叫了KeyFrameSet
的getValue
方法:
//KeyFrameSet.getValue
Keyframe prevKeyframe = mFirstKeyframe;
for (int i = 1; i < mNumKeyframes; ++i) {
Keyframe nextKeyframe = mKeyframes.get(i);
if (fraction < nextKeyframe.getFraction()) {
final TimeInterpolator interpolator = nextKeyframe.getInterpolator();
final float prevFraction = prevKeyframe.getFraction();
float intervalFraction = (fraction - prevFraction) /
(nextKeyframe.getFraction() - prevFraction);
// Apply interpolator on the proportional duration.
if (interpolator != null) {
intervalFraction = interpolator.getInterpolation(intervalFraction);
}
return mEvaluator.evaluate(intervalFraction, prevKeyframe.getValue(),
nextKeyframe.getValue());
}
prevKeyframe = nextKeyframe;
}
複製程式碼
KeyFrameSet
的getValue
方法,會通過傳入的差值變數,匹配到 最接近且不超過 的一個時間幀,並通過mEvaluator
計算器計算返回給上層呼叫。我們再回朔一下ValueAnimator
的繪製過程:
ValueAnimator
呼叫start方法將自己放入到AnimatorHandler
的佇列中去,AnimatorHandler
將post
一個FrameCallback
到Choreographer
的動畫訊息處理佇列中去。
2.當收到一條VSYNC
訊息或者是繪製指令,將回撥ValueAnimator
的doAnimationFrame
方法,而doAnimationFrame()
方法中會呼叫animateBasedOnTime()
-> animateValue()
方法用於計算。
animateValue()
方法計算中會呼叫PropertyValuesHolder[] mValues
的calculateValue
方法用於計算當前時刻的差值:
void calculateValue(float fraction) {
mAnimatedValue = mKeyframeSet.getValue(fraction);
}
複製程式碼
並將當前值儲存在mAnimatedValue
變數中去。
我們通過上面的流程解釋了
Animator
動畫過程中的差值計算,那麼接下去,我們就需要把這個值注入到我們的控制元件屬性中去,這樣才能夠實現動畫的效果。那麼我們計算好了屬性值,我們需要在哪兒注入到我們的控制元件物件中去呢?而且,屬性可能對應的是不同的型別,我們又如何區分不同的型別呢? 我們現在解答第一個問題: 我們看下一下這個例子:
View view = ...;
view.animate().translationX(500).start();
複製程式碼
這時候,我們會看見我們的控制元件
view
沿著x軸方向正方向平移500個單位
。實際上,這種動畫的實現就是用的我們上面的屬性動畫,而屬性動畫的計算過程跟我們上述的一摸一樣。那麼它又是如何將計算好的結果設定到View
物件上的呢? 首先,View.animate()
方法返回的是一個ViewPropertyAnimator
,不要被它的名字所誤導,它並不是一個Animator
,它的作用其實類似一個Animator
的Builder
物件
public ViewPropertyAnimator animate() {
if (mAnimator == null) {
mAnimator = new ViewPropertyAnimator(this);
}
return mAnimator;
}
複製程式碼
當
ViewPropertyAnimator
呼叫start
和startAnimation
方法的時候,ViewPropertyAnimator
會真正的構造我們的屬性動畫ValueAnimator
。
//code ViewPropertyAnimator.java
public void start() {
...
startAnimation();
}
private void startAnimation() {
ValueAnimator animator = ValueAnimator.ofFloat(1.0f);
....
animator.addUpdateListener(mAnimatorEventListener);//增加回撥
animator.addListener(mAnimatorEventListener);//增加回撥
...
animator.start();
}
複製程式碼
這裡,
ViewPropertyAnimator
會給生成的ValueAnimator
物件增加非常重要的介面mAnimatorEventListener
。我們知道,ValueAnimator
在計算完每一幀以後,都會回撥AnimatorUpdateListener
介面的onAnimationUpdate
方法:
//code ValueAnimator.java
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);//回撥介面
}
}
}
複製程式碼
也就是說,每次計算完後,ValueAnimator都會回撥mAnimatorEventListener物件的onAnimationUpdate方法,而在mAnimatorEventListener物件的實現中,將會把計算好的值賦予View物件:
//code AnimatorEventListener.java
@Override
public void onAnimationUpdate(ValueAnimator animation) {
PropertyBundle propertyBundle = mAnimatorMap.get(animation);
if (propertyBundle == null) {
// Shouldn't happen, but just to play it safe
return;
}
boolean hardwareAccelerated = mView.isHardwareAccelerated();
boolean alphaHandled = false;
if (!hardwareAccelerated) {
mView.invalidateParentCaches();
}
float fraction = animation.getAnimatedFraction();
int propertyMask = propertyBundle.mPropertyMask;
if ((propertyMask & TRANSFORM_MASK) != 0) {
mView.invalidateViewProperty(hardwareAccelerated, false);
}
ArrayList<NameValuesHolder> valueList = propertyBundle.mNameValuesHolder;
if (valueList != null) {
int count = valueList.size();
for (int i = 0; i < count; ++i) {
NameValuesHolder values = valueList.get(i);
float value = values.mFromValue + fraction * values.mDeltaValue;
if (values.mNameConstant == ALPHA) {
alphaHandled = mView.setAlphaNoInvalidation(value);
} else {
setValue(values.mNameConstant, value);//設定值
}
}
}
....
}
複製程式碼
這裡主要呼叫了個叫
setValue(values.mNameConstant, value);
的方法,而此方法會通過傳入的名字常量執行不同的操作:
private void setValue(int propertyConstant, float value) {
final View.TransformationInfo info = mView.mTransformationInfo;
final RenderNode renderNode = mView.mRenderNode;
switch (propertyConstant) {
case TRANSLATION_X:
renderNode.setTranslationX(value);
break;
case TRANSLATION_Y:
renderNode.setTranslationY(value);
break;
case TRANSLATION_Z:
renderNode.setTranslationZ(value);
break;
case ROTATION:
renderNode.setRotation(value);
break;
case ROTATION_X:
renderNode.setRotationX(value);
break;
case ROTATION_Y:
renderNode.setRotationY(value);
break;
....
}
}
複製程式碼