1. 前言
上一篇文章《Android Animation運(yùn)行原理詳解》介紹了插間動(dòng)畫的原理脐嫂,而Android3.0之后引進(jìn)了一種動(dòng)畫實(shí)現(xiàn)——屬性動(dòng)畫,放在以前可能會(huì)因?yàn)橐嫒?.0以前系統(tǒng)而小猶豫下曹动,但現(xiàn)在3.0以上系統(tǒng)已占有率已達(dá)97%以上(來(lái)自Android Studio統(tǒng)計(jì)數(shù)據(jù)),市場(chǎng)上許多應(yīng)用甚至已經(jīng)將4.0作為最低兼容版本了涧郊,因此屬性動(dòng)畫基本就是標(biāo)配了钾虐,再說(shuō)就算真的有老古董應(yīng)用想要兼容3.0以前系統(tǒng)也可以使用開源庫(kù)NineOldAndroids來(lái)享受屬性動(dòng)畫帶來(lái)的快感。這種情況下吏廉,理解屬性動(dòng)畫的運(yùn)行原理,分析屬性動(dòng)畫相對(duì)于插間動(dòng)畫而言有哪些優(yōu)勢(shì)就比較有意義了惰许,而且屬性動(dòng)畫似乎已經(jīng)慢慢取代插間動(dòng)畫而成為動(dòng)畫的主流實(shí)現(xiàn)方式了席覆。本文主要分享我在學(xué)習(xí)屬性動(dòng)畫的過(guò)程中的一些收獲,包括:屬性動(dòng)畫包含哪些基本元素汹买;屬性動(dòng)畫的運(yùn)行流程是怎樣的佩伤;屬性動(dòng)畫是怎么對(duì)View起作用的;怎么組合并運(yùn)行多個(gè)屬性動(dòng)畫晦毙;屬性動(dòng)畫的幀率決定者Choreographer
是如何工作的生巡。
2. 屬性動(dòng)畫的基本元素
屬性動(dòng)畫跟插間動(dòng)畫一樣會(huì)包含動(dòng)畫相關(guān)的屬性,如動(dòng)畫時(shí)長(zhǎng)见妒、延遲時(shí)間孤荣、插間器等等,為了后面分析動(dòng)畫運(yùn)行流程時(shí)概念更加明確须揣,這里我摘抄了ValueAnimator
源碼中的字段盐股,并做了相應(yīng)的注解,對(duì)Animator有過(guò)了解的同學(xué)可以直接跳過(guò)耻卡,不熟悉的同學(xué)建議先掃一眼疯汁,然后重點(diǎn)關(guān)注PropertyValuesHolder
這個(gè)屬性相關(guān)的介紹。
// 初始化函數(shù)是否被調(diào)用
boolean mInitialized = false;
// 動(dòng)畫時(shí)長(zhǎng)
private long mDuration = (long)(300 * sDurationScale);
private long mUnscaledDuration = 300;
// 動(dòng)畫延時(shí)
private long mStartDelay = 0;
private long mUnscaledStartDelay = 0;
// 動(dòng)畫重復(fù)模式及次數(shù)
private int mRepeatCount = 0;
private int mRepeatMode = RESTART;
// 插間器卵酪,參看http://cogitolearning.co.uk/?p=1078
private TimeInterpolator mInterpolator = sDefaultInterpolator;
// 動(dòng)畫開始運(yùn)行的時(shí)間點(diǎn)
long mStartTime;
// 是否需要在掉幀的時(shí)候調(diào)整動(dòng)畫開始時(shí)間點(diǎn)
boolean mStartTimeCommitted;
// 動(dòng)畫是否反方向運(yùn)行幌蚊,當(dāng)repeatMode=REVERSE是會(huì)每個(gè)動(dòng)畫周期反轉(zhuǎn)一次
private boolean mPlayingBackwards = false;
// 當(dāng)前動(dòng)畫在一個(gè)動(dòng)畫周期中所處位置
private float mCurrentFraction = 0f;
// 動(dòng)畫是否延時(shí)
private boolean mStartedDelay = false;
// 動(dòng)畫完成延時(shí)的時(shí)間點(diǎn)
private long mDelayStartTime;
// 動(dòng)畫當(dāng)前所處的狀態(tài):STOPPED, RUNNING, SEEKED
int mPlayingState = STOPPED;
// 動(dòng)畫是否被啟動(dòng)
private boolean mStarted = false;
// 動(dòng)畫是否被執(zhí)行(以動(dòng)畫第一幀被計(jì)算為界)
private boolean mRunning = false;
// 回調(diào)監(jiān)聽器
private boolean mStartListenersCalled = false; // 確保AnimatorListener.onAnimationStart(Animator)僅被調(diào)用一次
ArrayList<AnimatorListener> mListeners = null; // start,end,cancel,repeat回調(diào)
ArrayList<AnimatorPauseListener> mPauseListeners = null; // pause, resume回調(diào)
ArrayList<AnimatorUpdateListener> mUpdateListeners = null; // value更新回調(diào)
以上屬性都是動(dòng)畫的基本屬性以及運(yùn)行過(guò)程的狀態(tài)記錄,跟插間動(dòng)畫差別不大溃卡,而屬性動(dòng)畫相對(duì)于插間動(dòng)畫來(lái)件引入了一些新的概念:可以暫停和恢復(fù)溢豆、可以調(diào)整進(jìn)度,這些概念的引入塑煎,讓動(dòng)畫的概念更加飽滿起來(lái)沫换,讓動(dòng)畫有了視頻播放的概念,而跟這些新概率相關(guān)的屬性主要包括:
// 動(dòng)畫是否被暫停
boolean mPaused = false;
// 動(dòng)畫暫停時(shí)間點(diǎn),用于在動(dòng)畫被恢復(fù)的時(shí)候調(diào)整mStartTime以確保動(dòng)畫能優(yōu)雅地繼續(xù)運(yùn)行
private long mPauseTime;
// 動(dòng)畫是否從暫停中被恢復(fù)讯赏,用于表明動(dòng)畫可以調(diào)整mStartTime了
private boolean mResumed = false;
// 動(dòng)畫被設(shè)定的進(jìn)度位置垮兑,具體功效見后文對(duì)動(dòng)畫流程的分析
float mSeekFraction = -1;
除了上面這些動(dòng)畫屬性以外,Animator還包含了一個(gè)有別于Animation的屬性漱挎,那就是PropertyValuesHolder
系枪,PropertyValuesHolder
是用來(lái)保存某個(gè)屬性property
對(duì)應(yīng)的一組值,這些值對(duì)應(yīng)了一個(gè)動(dòng)畫周期中的所有關(guān)鍵幀磕谅。其實(shí)私爷,動(dòng)畫說(shuō)到底是由動(dòng)畫幀組成的,Animator可以設(shè)定并保存整個(gè)動(dòng)畫周期中的關(guān)鍵幀膊夹,然后根據(jù)這些關(guān)鍵幀計(jì)算出動(dòng)畫周期中任一時(shí)間點(diǎn)對(duì)應(yīng)的動(dòng)畫幀的動(dòng)畫數(shù)據(jù)衬浑,而每一幀的動(dòng)畫數(shù)據(jù)里都包含了一個(gè)時(shí)間點(diǎn)屬性fraction
以及一個(gè)動(dòng)畫值mValue
,從而實(shí)現(xiàn)根據(jù)當(dāng)前的時(shí)間點(diǎn)計(jì)算當(dāng)前的動(dòng)畫值放刨,然后用這個(gè)動(dòng)畫值去更新property
對(duì)應(yīng)的屬性工秩,這也是為什么Animator被稱為屬性動(dòng)畫的原因,因?yàn)樗恼麄€(gè)動(dòng)畫過(guò)程實(shí)際上就是不斷計(jì)算并更新對(duì)象的屬性:
// 保存property及其values的數(shù)組
PropertyValuesHolder[] mValues;
HashMap<String, PropertyValuesHolder> mValuesMap;
在這里必須要詳細(xì)分析下PropertyValuesHolder
进统,因?yàn)檫@關(guān)系到后面對(duì)動(dòng)畫流程的理解助币。注意了,我要出大招了:
上面這張圖詳細(xì)地描述了PropertyValuesHolder
的組成螟碎,我知道大家一般是不愿意看這張圖的眉菱,但是為什么我還要放在這?因?yàn)槲叶籍嬃说舴郑环旁趺茨茱@現(xiàn)出我的工作量- -俭缓!但是作為一個(gè)正義的程序員,我會(huì)用語(yǔ)言描述來(lái)拯救你們這些小懶蟲叉抡,PropertyValuesHolder
由Property
及Keyframes
組成尔崔,其中Property
用于描述屬性的特征:如屬性名以及屬性類型,并提供set及get方法用于獲取及設(shè)定給定Target的對(duì)應(yīng)屬性值褥民;Keyframes
由一組關(guān)鍵幀Keyframe
組成季春,每一個(gè)關(guān)鍵幀由fraction及value來(lái)定量描述,于是Keyframes
可以根據(jù)給定的fraction定位到兩個(gè)關(guān)鍵幀消返,這兩個(gè)關(guān)鍵幀的fraction組成的區(qū)間包含給定的fraction载弄,然后根據(jù)定位到的兩個(gè)關(guān)鍵幀以及設(shè)定插間器及求值器就可以計(jì)算出給定fraction對(duì)應(yīng)的value。于是撵颊,PropertyValuesHolder
的整個(gè)工作流程也就呼之欲出了宇攻,首先通過(guò)setObjectValues
等函數(shù)來(lái)初始化關(guān)鍵幀組mKeyframes
,必要的情況下(如ObjectAnimator
)可以通過(guò)setStartValue
及setEndValue
來(lái)設(shè)定第一幀及最末幀的value倡勇,以上工作只是完成了PropertyValuesHolder
的初始化逞刷,之后就可以由Animator
在繪制動(dòng)畫幀的時(shí)候通過(guò)fraction來(lái)調(diào)用calculateValue
計(jì)算該fraction對(duì)應(yīng)的value(實(shí)際上是由mKeyframes
的getValue
方法做出最終計(jì)算),獲得對(duì)應(yīng)的value之后,一方面可以通過(guò)getAnimatedValue
提供給Animator
使用夸浅,另一方面也可以通過(guò)setAnimatedValue
方法直接將該值設(shè)定到相應(yīng)Target中去仑最,這樣PropertyValuesHolder
的職責(zé)也就完成了。有了PropertyValuesHolder
的鼎力支持之后帆喇,動(dòng)畫也就可以開始正常的運(yùn)轉(zhuǎn)起來(lái)了警医,具體的運(yùn)轉(zhuǎn)流程又是怎樣的咧?且聽下節(jié)講解坯钦。
3. 屬性動(dòng)畫的運(yùn)行流程
通過(guò)上一小節(jié)的介紹预皇,我想各位看客應(yīng)該對(duì)動(dòng)畫的組成元素有個(gè)基本的了解了,接下來(lái)就讓我們看看這些基本元素組合在一起之后能誕生怎樣的奇妙功效婉刀,讓我們揭開面紗一睹美人芳顏吟温。
屬性動(dòng)畫的運(yùn)轉(zhuǎn)流程大體可分為三種類型:善始善終型、英年早逝型突颊、命運(yùn)多舛型溯街,但不管哪種類型首先必須進(jìn)行以下這些必要的初始化工作:
- 通過(guò)
setIntValues
、setFloatValues
洋丐、setObjectValues
或setValues
初始化PropertyValuesHolder
數(shù)組 - 設(shè)定
mDuration
、mStartDelay
挥等、mRepeatCount
友绝、mRepeatMode
、mInterpolator
肝劲、各種監(jiān)聽器等動(dòng)畫相關(guān)參數(shù)
完成動(dòng)畫的初始化工作之后迁客,隨著start
的調(diào)用,動(dòng)畫正式開始辞槐。
(1)善始善終型動(dòng)畫
善始善終型動(dòng)畫描述的是一種無(wú)病而終的動(dòng)畫人生掷漱,這樣的動(dòng)畫一旦start
就沿著既定的路線一直跑到終點(diǎn),不快不慢榄檬、不長(zhǎng)不短卜范。
在start
函數(shù)中,首先會(huì)嘗試獲取或創(chuàng)建一個(gè)AnimationHandler
鹿榜,這里要是不解釋下AnimationHandler
可能就忽悠不下去了海雪,因此我們來(lái)看看這是個(gè)什么鬼。
protected static ThreadLocal<AnimationHandler> sAnimationHandler =
new ThreadLocal<AnimationHandler>();
根據(jù)官方解釋以及源碼分析可以發(fā)現(xiàn):這家伙就是一個(gè)定時(shí)任務(wù)處理器舱殿,根據(jù)Choreographer
的脈沖周期性地完成指定的任務(wù)奥裸,由于它是一個(gè)線程安全的靜態(tài)變量,因此運(yùn)行在同一線程中的所有Animator共用一個(gè)定時(shí)任務(wù)處理器沪袭,這樣的好處在于:一方面可以保證Animator中計(jì)算某一時(shí)刻動(dòng)畫幀是在同一線程中運(yùn)行的湾宙,避免了多線程同步的問題;另一方面,該線程下所有動(dòng)畫共用一個(gè)處理器侠鳄,可以讓這些動(dòng)畫有效地進(jìn)行同步埠啃,從而讓動(dòng)畫效果更加優(yōu)雅。至于AnimationHandler
具體用來(lái)做哪些任務(wù)畦攘,我們看看動(dòng)畫怎么蹦跶的就明白了霸妹。
成功獲取到AnimationHandler
之后,會(huì)做如下處理:
mPlayingState = STOPPED; // 當(dāng)前狀態(tài)為STOPPED
mStarted = true; // 動(dòng)畫啟動(dòng)標(biāo)志位置位
mStartedDelay = false; // 動(dòng)畫未完成延時(shí)標(biāo)志位復(fù)位
mPaused = false; // 動(dòng)畫暫停標(biāo)志位復(fù)位
若動(dòng)畫未設(shè)定mStartDelay
知押,還會(huì)如下額外操作:
mStartTime = currentTime; // 動(dòng)畫起始時(shí)間為當(dāng)前時(shí)間
mStartTimeCommitted = true; // 動(dòng)畫起始時(shí)間不可調(diào)整
animateValue(fraction); // 計(jì)算第一幀動(dòng)畫值
mRunning = true; // 動(dòng)畫運(yùn)行標(biāo)志位置位
notifyStartListeners(); // 回調(diào)AnimatorListener.onAnimationStart
完成所有啟動(dòng)操作之后叹螟,會(huì)將該動(dòng)畫加入AnimationHandler.mPendingAnimations
這個(gè)等待列表,接著就正式開啟AnimationHandler
的定時(shí)任務(wù):
animationHandler.start()
-> animationHandler.scheduleAnimation()
-> mChoreographer.postCallback(Choreographer.CALLBACK_ANIMATION, mAnimate, null);
-> animationHandler.doAnimationFrame(mChoreographer.getFrameTime());
BOSS來(lái)襲!!!AnimationHandler.doAnimationFrame
就是動(dòng)畫的Studio台盯,負(fù)責(zé)動(dòng)畫生命周期的處理罢绽,用下面這個(gè)流程圖來(lái)簡(jiǎn)單描述:
注意,這個(gè)流程圖跟普通的流程圖意義稍微有點(diǎn)區(qū)別静盅,那就是整個(gè)流程不一定是在一幀內(nèi)同時(shí)完成的良价,一個(gè)動(dòng)畫可能需要跨越多幀才能從START走到END,比如:動(dòng)畫可能延遲了3幀才正式開始蒿叠,然后做了10幀動(dòng)畫才最后結(jié)束明垢。流程清楚了之后,我們來(lái)分析下流程中涉及的函數(shù)具體的邏輯是怎樣的市咽,但分析之前有必要先說(shuō)明下AnimationHandler
中的保存動(dòng)畫的幾個(gè)列表:
// 保存start被調(diào)用且尚未到達(dá)第一幀的動(dòng)畫
protected final ArrayList<ValueAnimator> mPendingAnimations = new ArrayList<ValueAnimator>();
// 保存第一幀已到達(dá)且mStartDelay不等于0的動(dòng)畫痊银,等待調(diào)用Animator.delayedAnimationFrame
protected final ArrayList<ValueAnimator> mDelayedAnims = new ArrayList<ValueAnimator>();
// 保存延時(shí)已完成的動(dòng)畫,等待調(diào)用Animator.startAnimation
private final ArrayList<ValueAnimator> mReadyAnims = new ArrayList<ValueAnimator>();
// 保存正在運(yùn)行的動(dòng)畫施绎,該列表中動(dòng)畫mRunning為true溯革,等待調(diào)用Animator.doAnimationFrame
protected final ArrayList<ValueAnimator> mAnimations = new ArrayList<ValueAnimator>();
// 保存已完成的動(dòng)畫,等待調(diào)用Animator.endAnimation
private final ArrayList<ValueAnimator> mEndingAnims = new ArrayList<ValueAnimator>();
了解了這幾個(gè)列表之后谷醉,我們就可以描述AnimationHandler.doAnimationFrame
做了啥了:
- 若
mPendingAnimations
不為空致稀,則遍歷其內(nèi)所有Animator做如下處理:-
mStartDelay==0
的Animator,調(diào)用Animator.startAnimation
俱尼,該函數(shù)處理如下:- 調(diào)用
Animator.initAnimation
抖单,初始化所有PropertyValuesHolder
- 將該Animator放入
AnimationHandler.mAnimations
- 調(diào)用
AnimatorListener.onAnimationStart
- 調(diào)用
-
mStartDelay==0
的Animator,放入AnimationHandler.mDelayedAnims
-
- 若
mDelayedAnims
不為空遇八,則遍歷其內(nèi)所有Animator做如下處理:- 調(diào)用
Animator.delayedAnimationFrame
臭猜,判斷該動(dòng)畫延時(shí)是否完成,若延時(shí)完成:-
mStartTime = mDelayStartTime + mStartDelay
押蚤,設(shè)定動(dòng)畫開始時(shí)間 -
mStartTimeCommitted = true
蔑歌,禁止調(diào)整動(dòng)畫開始時(shí)間 -
mPlayingState = RUNNING
,設(shè)置動(dòng)畫運(yùn)行狀態(tài)為RUNNING - 將該Animator放入
AnimationHandler.mReadyAnims
-
- 調(diào)用
- 若
mReadyAnims
不為空揽碘,則遍歷其內(nèi)所有Animator做如下處理:- 調(diào)用
Animator.startAnimation
- 設(shè)置
Animator.mRunning = true
- 調(diào)用
- 若
mAnimations
不為空次屠,則遍歷其內(nèi)所有Animator做如下處理:- 調(diào)用
Animator.doAnimationFrame
园匹,這個(gè)函數(shù)是動(dòng)畫的關(guān)鍵處理函數(shù):- 若
mPlayingState == STOPPED
,則初始化mStartTime
為該動(dòng)畫幀時(shí)間 - 調(diào)用
animationFrame(long currentTime)
劫灶,做如下處理:
boolean animationFrame(long currentTime) { boolean done = false; switch (mPlayingState) { case RUNNING: case SEEKED: // 計(jì)算當(dāng)前動(dòng)畫幀相對(duì)于上一幀動(dòng)畫而言包含的動(dòng)畫周期數(shù) float fraction = mDuration > 0 ? (float)(currentTime - mStartTime) / mDuration : 1f; if (mDuration == 0 && mRepeatCount != INFINITE) { // 若動(dòng)畫周期為0裸违,則可以直接結(jié)束動(dòng)畫 mCurrentIteration = mRepeatCount; if (!mReversing) { mPlayingBackwards = false; } } if (fraction >= 1f) { // 動(dòng)畫周期數(shù)大于1的情況下,需對(duì)動(dòng)畫進(jìn)行repeat或者結(jié)束動(dòng)畫 if (mCurrentIteration < mRepeatCount || mRepeatCount == INFINITE) { // repeat動(dòng)畫 if (mListeners != null) { int numListeners = mListeners.size(); for (int i = 0; i < numListeners; ++i) { // 調(diào)用AnimationListener.onAnimationRepeat mListeners.get(i).onAnimationRepeat(this); } } if (mRepeatMode == REVERSE) { mPlayingBackwards = !mPlayingBackwards; } // 累加動(dòng)畫repeat次數(shù) mCurrentIteration += (int) fraction; // 調(diào)整fraction至[0.0f,1.0f) fraction = fraction % 1f; // 調(diào)整動(dòng)畫開始時(shí)間 mStartTime += mDuration; } else { // 結(jié)束動(dòng)畫 done = true; fraction = Math.min(fraction, 1.0f); } } if (mPlayingBackwards) { fraction = 1f - fraction; } // 計(jì)算當(dāng)前動(dòng)畫幀的動(dòng)畫數(shù)據(jù) animateValue(fraction); break; } return done; }
- 若
- 調(diào)用
- `animateValue(fraction)`函數(shù)如下:
```java
void animateValue(float fraction) {
fraction = mInterpolator.getInterpolation(fraction);
mCurrentFraction = fraction;
int numValues = mValues.length;
for (int i = 0; i < numValues; ++i) {
// 通過(guò)PropertyValuesHolder計(jì)算當(dāng)前動(dòng)畫幀的動(dòng)畫值
mValues[i].calculateValue(fraction);
}
if (mUpdateListeners != null) {
int numListeners = mUpdateListeners.size();
for (int i = 0; i < numListeners; ++i) {
// 調(diào)用AnimatorUpdateListener.onAnimationUpdate本昏,
// 在回調(diào)中可以通過(guò)Animator.getAnimatedValue()獲取當(dāng)前動(dòng)畫幀的數(shù)據(jù)進(jìn)行最終的動(dòng)畫處理(如調(diào)整Target相應(yīng)的屬性值)
mUpdateListeners.get(i).onAnimationUpdate(this);
}
}
}
- 若
mEndingAnims
不為空供汛,則遍歷其內(nèi)所有Animator做如下處理:- 調(diào)用
Animator.endAnimation
,該函數(shù)主要做掃尾工作:- 把該動(dòng)畫從
AnimationHandler
的所有列表中清除 - 若未調(diào)用過(guò)
AnimatorListener.onAnimationStart
涌穆,則調(diào)用 - 調(diào)用
AnimatorListener.onAnimationEnd
- 復(fù)位動(dòng)畫所有狀態(tài):如
mPlayingState = STOPPED
怔昨、mRunning=false
、mStarted = false
等等
- 把該動(dòng)畫從
- 調(diào)用
- 若
mAnimations
或mDelayedAnims
不為空宿稀,則對(duì)下一幀進(jìn)行定時(shí)趁舀。
通過(guò)上面這個(gè)描述應(yīng)該能比較清楚的理解動(dòng)畫的整個(gè)流程了,但是這里其實(shí)我有一個(gè)疑問尚未解惑:在animationFrame(long currentTime)
計(jì)算得到fraction
后祝沸,當(dāng)fraction>=1.0f
時(shí)會(huì)對(duì)迭代次數(shù)以及動(dòng)畫開始時(shí)間進(jìn)行調(diào)整矮烹,代碼如下:
// 計(jì)算fraction
fraction = mDuration > 0 ? (float)(currentTime - mStartTime) / mDuration : 1f;
// 調(diào)整迭代次數(shù)以及動(dòng)畫開始時(shí)間
mCurrentIteration += (int) fraction;
fraction = fraction % 1f;
mStartTime += mDuration;
fraction
的計(jì)算及調(diào)整、mCurrentIteration
的累積并不難理解罩锐,但是對(duì)mStartTime
的調(diào)整就有點(diǎn)詭異了:無(wú)論當(dāng)前跨越多少個(gè)動(dòng)畫周期奉狈,動(dòng)畫的起始時(shí)間只向前調(diào)整一個(gè)周期,而我查了發(fā)現(xiàn)也沒有其他地方會(huì)在下一幀動(dòng)畫運(yùn)算前對(duì)mStartTime
再做調(diào)整涩惑;那么在下一幀動(dòng)畫中計(jì)算fraction
時(shí)嘹吨,若上一幀動(dòng)畫跨越的動(dòng)畫周期數(shù)大于等于2,則這一次計(jì)算得到的fraction
會(huì)比正尘城猓跨越周期數(shù)多,這意味著動(dòng)畫實(shí)際的運(yùn)行時(shí)長(zhǎng)會(huì)比理論上的時(shí)長(zhǎng)短碰纬,當(dāng)然這僅僅發(fā)生在當(dāng)mDuration < 0.5 * frameInterval
時(shí)萍聊,其中frameInterval為兩幀動(dòng)畫的間隔時(shí)長(zhǎng)。盡管對(duì)于通常僅為16ms的frameInterval
來(lái)說(shuō)悦析,這種情形應(yīng)該算是小概率事件寿桨,但是Android為何要如此處理,我想應(yīng)該是有一個(gè)比較合理的理由强戴,只是我目前尚未理解亭螟,希望了解緣由的小伙伴能指點(diǎn)一二。
(2)英年早逝型動(dòng)畫
不是每一個(gè)動(dòng)畫都能正常走完自己的動(dòng)畫人生骑歹,有些動(dòng)畫可能需要在必要的時(shí)候?yàn)橥瓿赡硞€(gè)絢爛的效果而英勇犧牲预烙。這樣的情況通常有兩種:cancel
和end
。接下我們就一次分析下這兩種情形:
1) Animator.cancel
cancel
只會(huì)處理那些正在運(yùn)行或者等待開始運(yùn)行的動(dòng)畫道媚,具體的處理邏輯是這樣的:
- 若未調(diào)用過(guò)
AnimatorListener.onAnimationStart
扁掸,則調(diào)用 - 調(diào)用
AnimatorListener.onAnimationCancel
- 調(diào)用
Animator.endAnimation
:- 把該動(dòng)畫從
AnimationHandler
的所有列表中清除 - 調(diào)用
AnimatorListener.onAnimationEnd
- 復(fù)位動(dòng)畫所有狀態(tài):如
mPlayingState = STOPPED
翘县、mRunning=false
、mStarted = false
等等
- 把該動(dòng)畫從
從上面的邏輯不難發(fā)現(xiàn)谴分,cancel
對(duì)回調(diào)的處理是比較完整的锈麸,但是cancel
被調(diào)用之后,動(dòng)畫的動(dòng)畫值會(huì)停留在當(dāng)前幀而不會(huì)繼續(xù)進(jìn)行計(jì)算牺蹄。
2) Animator.end
end
相對(duì)于cancel
來(lái)說(shuō)有兩個(gè)區(qū)別:一個(gè)是會(huì)處理所有動(dòng)畫忘伞;另一個(gè)是會(huì)計(jì)算最末一幀動(dòng)畫值。其具體的處理邏輯如下所示:
- 若動(dòng)畫尚未開始:調(diào)用
Animatior.startAnimation
讓動(dòng)畫處于正常運(yùn)行狀態(tài) - 計(jì)算最后一幀動(dòng)畫的動(dòng)畫值:
animateValue(mPlayingBackwards ? 0f : 1f)
- 結(jié)束動(dòng)畫:調(diào)用
endAnimation
這兩種處理都會(huì)導(dǎo)致動(dòng)畫的非正常結(jié)束沙兰,需要注意的是cancel
會(huì)保留當(dāng)前的動(dòng)畫值氓奈,而end
會(huì)計(jì)算最末幀的動(dòng)畫值。
(3)命運(yùn)多舛型動(dòng)畫
之前提到過(guò)僧凰,屬性動(dòng)畫相對(duì)于插間動(dòng)畫而言更多的體現(xiàn)出了一種播放視頻的感覺:可以暫停和恢復(fù)探颈、可以調(diào)整進(jìn)度,這樣的動(dòng)畫人生是走走停停的训措,有時(shí)候一不小心還會(huì)倒退伪节,過(guò)得并不那么一帆風(fēng)順,是多舛的命運(yùn)绩鸣。我們來(lái)一一分析一下:
1) pause
當(dāng)pause
被調(diào)用的時(shí)候怀大,僅在動(dòng)畫已開始(mStarted==true
)且當(dāng)前為非暫停狀態(tài)時(shí)才進(jìn)行以下處理:
- 置位:
mPaused = true
- 調(diào)用
AnimatorPauseListener.onAnimationPause
- 復(fù)位
mResumed = false
- 清空暫停時(shí)間:
mPauseTime = -1
做完這些處理之后,就靜靜地等風(fēng)來(lái)(等下一幀動(dòng)畫的到來(lái))呀闻,當(dāng)風(fēng)來(lái)了之后化借,delayedAnimationFrame
或doAnimationFrame
被調(diào)用,此時(shí)若仍然處于暫停狀態(tài)捡多,就會(huì)做如下截?fù)簦?/p>
if (mPaused) {
if (mPauseTime < 0) {
mPauseTime = frameTime;
}
return false;
}
這樣就阻止了動(dòng)畫的正常運(yùn)行蓖康,并記錄下來(lái)動(dòng)畫暫停的時(shí)間,確崩菔郑恢復(fù)之后能讓動(dòng)畫調(diào)整到暫停之前的動(dòng)畫點(diǎn)正常運(yùn)行蒜焊,具體怎么起作用就要看resume這小子的了。
2) resume
當(dāng)resume
被調(diào)用的時(shí)候科贬,僅當(dāng)當(dāng)前是暫停狀態(tài)時(shí)泳梆,會(huì)做如下處理:
- 置位:
mResumed = true
- 復(fù)位:
mPaused = false
- 調(diào)用
AnimatorPauseListener.onAnimationResume
當(dāng)風(fēng)再次來(lái)臨,delayedAnimationFrame
或doAnimationFrame
被調(diào)用榜掌,此時(shí)若處于恢復(fù)狀態(tài)(mResume==true
)优妙,就會(huì)做如下補(bǔ)償處理:
// delayedAnimationFrame的處理
if (mResumed) {
mResumed = false;
if (mPauseTime > 0) {
mDelayStartTime += (currentTime - mPauseTime);
}
}
// doAnimationFrame的處理
if (mResumed) {
mResumed = false;
if (mPauseTime > 0) {
mStartTime += (frameTime - mPauseTime);
mStartTimeCommitted = false;
}
}
這樣就讓暫停的時(shí)間從動(dòng)畫的運(yùn)行過(guò)程中消除,就好像從來(lái)沒暫停過(guò)一樣憎账,不帶走一片云彩套硼。
3) seek
我想每個(gè)人看電影的時(shí)候,或多或少會(huì)有這樣的經(jīng)歷:看到一個(gè)特別唯美或者特別感動(dòng)的畫面胞皱,總?cè)滩蛔∠隢次回放熟菲;又或看到一個(gè)無(wú)聊或爛俗的橋段看政,總吐槽著直接跳過(guò)。而對(duì)動(dòng)畫進(jìn)行seek
就類似于你拖動(dòng)電影的進(jìn)度條抄罕,這樣可以讓動(dòng)畫從動(dòng)畫過(guò)程中的任意一個(gè)時(shí)刻開始運(yùn)行允蚣,seek
是通過(guò)setCurrentPlayTime(long playTime)
或者setCurrentFraction(float fraction)
來(lái)實(shí)現(xiàn)的,實(shí)際上最終的邏輯都在setCurrentFraction
里面呆贿,這家伙主要干了下面這幾件事:
- 根據(jù)fraction計(jì)算并更新
mPlayingBackwards
嚷兔、mCurrentIteration
、mStartTime
等動(dòng)畫關(guān)鍵元素 - 調(diào)用
animateValue(fraction)
計(jì)算該fraction對(duì)應(yīng)的動(dòng)畫值 - 若動(dòng)畫為非運(yùn)行狀態(tài)(mPlayingState != RUNNING)做入,則設(shè)定
mPlayingState = SEEKED
做完這些事冒晰,動(dòng)畫的運(yùn)行狀態(tài)就被強(qiáng)行調(diào)整了,當(dāng)下一幀動(dòng)畫來(lái)臨時(shí)竟块,則會(huì)從強(qiáng)行設(shè)定的這個(gè)動(dòng)畫時(shí)間點(diǎn)繼續(xù)按正常的動(dòng)畫流程繼續(xù)運(yùn)行下去壶运,然而滔以,在這里有一個(gè)不可忽視的小細(xì)節(jié)燕鸽,那就是當(dāng)用seek
向前調(diào)整的時(shí)候會(huì)導(dǎo)致mStartTime
先于frameTime
豁延,這樣假設(shè)還是用mStartTime去調(diào)用animationFrame
就會(huì)導(dǎo)致animationFrame
中計(jì)算得到的fraction為負(fù)值蹬刷,因此細(xì)心的程序員們?cè)谡{(diào)用animationFrame
之前做了這樣的處理:Math.max(frameTime, mStartTime)
,這樣整個(gè)世界就清靜了文判,永遠(yuǎn)不會(huì)出現(xiàn)負(fù)值上祈。
到這為止藐守,動(dòng)畫的三大運(yùn)轉(zhuǎn)流程就講完了夺衍,其中有些個(gè)處理地比較漂亮的細(xì)節(jié)可能由于篇幅的問題這里并未提及狈谊,希望有心的小伙伴可以再去看看源碼,我想會(huì)有不少的收獲的沟沙。
4. 屬性動(dòng)畫與View的結(jié)合
前面長(zhǎng)篇大論了半天河劝,根本沒有具體講到屬性動(dòng)畫是怎么在View上起作用的,但其實(shí)細(xì)致看下來(lái)的小伙伴應(yīng)該很容易推測(cè)出怎么用屬性動(dòng)畫去實(shí)現(xiàn)View的變換:那就是根據(jù)計(jì)算出來(lái)的動(dòng)畫值去修改View的屬性矛紫,如alpha赎瞎、x、y含衔、scaleX、scaleY二庵、translationX贪染、translationY等等,這樣當(dāng)View重繪時(shí)就會(huì)產(chǎn)生作用催享,隨著View連續(xù)不斷地被重繪杭隙,你的眼中就會(huì)產(chǎn)生絢爛多彩的動(dòng)畫了。這就說(shuō)完了因妙,是不是感覺這一節(jié)也太精簡(jiǎn)了痰憎,相對(duì)于前一節(jié)來(lái)講簡(jiǎn)直讓人難以接受票髓。不要慌,我這么濫情的人是舍不得你們難過(guò)的铣耘,所以我要把ObjectAnimator
這個(gè)最常用的家伙祭出來(lái)拯救你們受傷的小心臟洽沟。
ObjectAnimator
是可以在動(dòng)畫幀計(jì)算完成之后直接對(duì)Target
屬性進(jìn)行修改的屬性動(dòng)畫類型,相對(duì)于ValueAnimator
來(lái)說(shuō)更加省心省力蜗细,為了造福廣大Androider裆操,ObjectAnimator
默默做了不少工作:
- 提供
setTarget
接口,用于設(shè)定動(dòng)畫過(guò)程中屬性修改的主體炉媒,值得注意的是踪区,若在動(dòng)畫已啟動(dòng)的情況下修改Taget會(huì)導(dǎo)致當(dāng)前動(dòng)畫被cancel
,然后等待下一次被start
-
initAnimation
會(huì)通過(guò)調(diào)用所有PropertyValuesHolder
的setupSetterAndGetter
方法實(shí)現(xiàn)對(duì)Property
的set及get方法的初始化吊骤,以方便后續(xù)對(duì)Target
對(duì)應(yīng)屬性值的修改 - 通過(guò)
setupStartValue
及setupEndValues
對(duì)各PropertyValuesHolder
種的首末幀數(shù)據(jù)的動(dòng)畫值進(jìn)行初始化 - 新增一個(gè)
mAutoCancel
屬性缎岗,當(dāng)mAutoCancel==true
時(shí),在start
的過(guò)程中會(huì)清除AnimationHandler
中對(duì)同一Target
及該Target
同一屬性進(jìn)行處理的其他動(dòng)畫 - 在動(dòng)畫的整個(gè)過(guò)程中白粉,若發(fā)現(xiàn)
Target
不再有效(動(dòng)畫這種保存的是Target
的弱引用)传泊,則cancel
該動(dòng)畫 - 最后,在
animationValue
函數(shù)中蜗元,調(diào)用PropertyValuesHolder.setAnimatedValue
對(duì)Target
的屬性進(jìn)行修改或渤。
以上,就是ObjectAnimator
背著ValueAnimator
額外做的各種“勾當(dāng)”奕扣,順帶再補(bǔ)充個(gè)小細(xì)節(jié)薪鹦,在Animator中保存PropertyValuesHolder
的是一個(gè)數(shù)組,而在函數(shù)animationValue
中會(huì)遍歷處理所有的PropertyValuesHolder
惯豆,因此一個(gè)動(dòng)畫實(shí)現(xiàn)多個(gè)屬性的同時(shí)修改是一件非常容易的事池磁。雖然知道了怎么實(shí)現(xiàn)一個(gè)動(dòng)畫中修改多個(gè)屬性,但是怎么實(shí)現(xiàn)多個(gè)動(dòng)畫的組合運(yùn)行還尚未可知楷兽,我們?cè)谙乱还?jié)里揭秘所有內(nèi)幕地熄。
5. 屬性動(dòng)畫的組合運(yùn)行
舉個(gè)例子來(lái)說(shuō)明組合運(yùn)行多個(gè)屬性動(dòng)畫的意思:我想在平移一個(gè)View的同時(shí)改變這個(gè)View的透明度,平移完成之后我需要放大整個(gè)View芯杀《丝迹看過(guò)《Android Animation運(yùn)行原理詳解》這篇文章的同學(xué)應(yīng)該知道插間動(dòng)畫是可以實(shí)現(xiàn)組合運(yùn)行多個(gè)動(dòng)畫的,但是其實(shí)現(xiàn)上只支持“在某個(gè)動(dòng)畫的同時(shí)做另外一個(gè)動(dòng)畫”這種“playTogether”的模式揭厚。然后到了屬性動(dòng)畫却特,我們會(huì)驚喜的發(fā)現(xiàn)組合動(dòng)畫AnimatorSet
除了可以實(shí)現(xiàn)“playTogether”模式(下文中“with”模式與此同義)之外,還支持“before”及“after”模式筛圆,這家伙簡(jiǎn)直是吃了竄天猴了——想上天啊裂明,但是不得不說(shuō),我喜歡23333~~
為了支持這些組合模式太援,AnimatorSet
下了血本闽晦,引入了Dependency
以及Node
這兩個(gè)數(shù)據(jù)結(jié)構(gòu)扳碍,其中Dependency
表示一條依賴規(guī)則:
private static class Dependency {
// 規(guī)則類型
public int rule;
static final int WITH = 0;
static final int AFTER = 1;
// 規(guī)則綁定的主體
public Node node;
}
而Node
用于在AnimatorSet
表征其包含的Animator
,其數(shù)據(jù)結(jié)構(gòu)如下:
private static class Node implements Cloneable {
// 對(duì)應(yīng)的動(dòng)畫
public Animator animation;
// 該Node包含的規(guī)則列表
public ArrayList<Dependency> dependencies = null;
// dependencies的副本仙蛉,應(yīng)用在動(dòng)畫運(yùn)行過(guò)程笋敞,避免損壞原始數(shù)據(jù)
public ArrayList<Dependency> tmpDependencies = null;
// 該節(jié)點(diǎn)依賴的節(jié)點(diǎn)
public ArrayList<Node> nodeDependencies = null;
// 依賴該節(jié)點(diǎn)的節(jié)點(diǎn)
public ArrayList<Node> nodeDependents = null;
// 該節(jié)點(diǎn)對(duì)應(yīng)的動(dòng)畫是否完成
public boolean done = false;
}
而在AnimatorSet
中是這樣來(lái)存儲(chǔ)Node
:
// 利用一個(gè)list及map來(lái)冗余地存儲(chǔ)該AnimatorSet包含的所有Node
private ArrayMap<Animator, Node> mNodeMap = new ArrayMap<Animator, Node>();
private ArrayList<Node> mNodes = new ArrayList<Node>();
// 用于存儲(chǔ)按動(dòng)畫運(yùn)行順序排好序的所有Node
private ArrayList<Node> mSortedNodes = new ArrayList<Node>();
有了這兩個(gè)利器之后,我們?cè)賮?lái)討論三種組合模式的實(shí)現(xiàn)就變得簡(jiǎn)單了捅儒,這三種組合模式都是通過(guò)一個(gè)叫Builder
的家伙來(lái)創(chuàng)建的液样,可以通過(guò)AnimatiorSet.play(Animator)
來(lái)創(chuàng)建Builder
,這也說(shuō)明每一個(gè)Builder
都會(huì)有一個(gè)規(guī)則主體Animator巧还,而用這個(gè)Builder
創(chuàng)建的規(guī)則都是以這個(gè)主體Animator為基準(zhǔn)的鞭莽,這也意味著該Builder
下多條規(guī)則之間是沒有直接必然的關(guān)聯(lián)的,但是規(guī)則之間可能會(huì)因?yàn)橹黧wAnimator而產(chǎn)生間接的關(guān)系麸祷,這個(gè)時(shí)候應(yīng)該舉個(gè)例子來(lái)說(shuō)明下這段抽象的描述澎怒,但是舉例之前必須先分析三種組合模式的具體實(shí)現(xiàn):
// 根據(jù)Animator獲取或創(chuàng)建Node
Node node = mNodeMap.get(anim);
if (node == null) {
node = new Node(anim);
mNodeMap.put(anim, node);
mNodes.add(node);
}
// with模式實(shí)現(xiàn)
Dependency dependency = new Dependency(mCurrentNode, Dependency.WITH);
node.addDependency(dependency);
// before模式實(shí)現(xiàn)
Dependency dependency = new Dependency(mCurrentNode, Dependency.AFTER);
node.addDependency(dependency);
// after模式實(shí)現(xiàn)
Dependency dependency = new Dependency(node, Dependency.AFTER);
mCurrentNode.addDependency(dependency);
從實(shí)現(xiàn)中可以看出在創(chuàng)建規(guī)則的時(shí)候?qū)嶋H上我們定義并記錄了Node之間的相互關(guān)系,同時(shí)我們發(fā)現(xiàn)由于在Dependency
并未定義“before”類型的規(guī)則阶牍,因此“before”模式實(shí)際是用“after”模式來(lái)間接實(shí)現(xiàn)的喷面。分析完這三種組合模式的具體實(shí)現(xiàn)之后,就可以繼續(xù)前面的舉例了:
AnimatorSet s = new AnimatorSet();
// 下面代碼產(chǎn)生的規(guī)則并不能確定anim2與anim3的先后關(guān)系
s.play(anim1).before(anim2).before(anim3);
// 下面代碼產(chǎn)生的規(guī)則可間接確定anim2與anim3的先后關(guān)系
s.play(anim1).before(anim2).after(anim3);
// 下面代碼產(chǎn)生的規(guī)則可完全確定anim1走孽、anim2惧辈、anim3之間的先后關(guān)系
s.play(anim1).before(anim2);
s.play(anim2).before(anim3);
這回應(yīng)該把之前那段抽象的描述解釋清楚了,但是另外一個(gè)懸念不知道各位有沒有發(fā)現(xiàn):我們?cè)趧?chuàng)建規(guī)則的時(shí)候只是記錄了Node
之間的相互關(guān)系磕瓷,但是這種相互關(guān)系具體是怎么起作用的尚未可知盒齿,真相就蘊(yùn)藏在AnimatorSet
對(duì)其包含的動(dòng)畫的調(diào)度過(guò)程中,說(shuō)曹操曹操到困食,下面我們就來(lái)分析AnimatorSet
是怎么管理這么多動(dòng)畫小朋友的边翁,要理清楚其中奧妙,還不得不提到兩個(gè)特殊的監(jiān)聽器:
1) DependencyListener implements AnimatorListener
DependencyListener
用于具化依賴規(guī)則:
public void onAnimationEnd(Animator animation) {
if (mRule == Dependency.AFTER) {
startIfReady(animation);
}
}
public void onAnimationStart(Animator animation) {
if (mRule == Dependency.WITH) {
startIfReady(animation);
}
}
當(dāng)動(dòng)畫開始或結(jié)束時(shí)硕盹,會(huì)分析以動(dòng)畫對(duì)應(yīng)Node
(設(shè)為NodeA)為依賴的Node
(設(shè)為NodeB)符匾,若將NodeA從NodeB的tmpDependencies
中移除之后tmpDependencies
不在包含其他Node
則說(shuō)明NodeB的啟動(dòng)條件已滿足。
2) AnimatorSetListener implements AnimatorListener
AnimatorSetListener
對(duì)于整個(gè)AnimatorSet
來(lái)說(shuō)僅有一個(gè)實(shí)例瘩例,該實(shí)例會(huì)被設(shè)定到所有被包含的Animator
中去啊胶,用于管理AnimatorSet
的回調(diào),如:僅當(dāng)所有Animator
均結(jié)束之后垛贤,才調(diào)用AnimatorSet
監(jiān)聽器的onAnimationEnd
焰坪;確保cancel
時(shí)對(duì)每一Animator
僅調(diào)用一次onAnimationCancel
。
了解了這兩個(gè)監(jiān)聽器之后南吮,我們就可以以AnimatorSet.start
為切入點(diǎn)一氣呵成地理解AnimatorSet
管理所有Animator
的邏輯琳彩,當(dāng)AnimatorSet.start
函數(shù)被調(diào)用時(shí)AnimatorSet
被正式激活:
-
根據(jù)
AnimatorSet
參數(shù)初始化包含的Animator
:- 禁用所有
Animator
的異步模式 - 若
mDuration >= 0
誊酌,則將該mDuration
設(shè)定至所有Animator
- 若
mInterpolator != null
部凑,則將該mInterpolator
設(shè)定至所有Animator
- 禁用所有
-
調(diào)用
sortNodes
函數(shù)根據(jù)Node
之間的依賴規(guī)則確定Node
中動(dòng)畫觸發(fā)的先后順序露乏,存儲(chǔ)在mSortedNodes
中,具體排序算法如下(這一段引用了源碼中的偽代碼注釋):- All nodes without dependencies become 'roots'
- while roots list is not null
- for each root r
- add r to sorted list
- remove r as a dependency from any other node
- any nodes with no dependencies are added to the roots list
- for each root r
-
分析
mSortedNodes
-
dependencies
為空的Node
涂邀,作為可直接啟動(dòng)的Node
放入nodesToStart
中 - 對(duì)于不可直接啟動(dòng)的
Node
瘟仿,針對(duì)其每一條依賴規(guī)則創(chuàng)建一個(gè)DependencyListener
加入其監(jiān)聽器列表 - 將
AnimatorSetListener
加入所有Node的監(jiān)聽器列表
-
-
根據(jù)該
AnimatorSet
是否需要延時(shí)分別處理:- 需要延時(shí):創(chuàng)建一個(gè)輔助延時(shí)Animator,設(shè)定其
mDuration
為AnimatorSet
的延時(shí)時(shí)長(zhǎng)比勉,并在延時(shí)Animator結(jié)束之后啟動(dòng)nodesToStart
中的所有動(dòng)畫 - 無(wú)需延時(shí):直接啟動(dòng)
nodesToStart
中的所有動(dòng)畫
- 需要延時(shí):創(chuàng)建一個(gè)輔助延時(shí)Animator,設(shè)定其
調(diào)用
AnimatorSet
中設(shè)定的監(jiān)聽器的onAnimationStart
若
AnimatorSet
不包含任何Animator(即mNodes為空)且無(wú)需延時(shí)劳较,則直接結(jié)束該AnimatorSet
,并調(diào)用AnimatorSet
中設(shè)定的監(jiān)聽器的onAnimationEnd
上面這一段話請(qǐng)?jiān)诶斫饬松衔闹刑岬降膬蓚€(gè)特殊監(jiān)聽器之后再閱讀浩聋,這樣你才能更加清楚的理解為什么在start
函數(shù)中這樣處理完了之后就可以實(shí)現(xiàn)根據(jù)Node
之間既定的依賴關(guān)系有序的完成所有動(dòng)畫观蜗。按常理,應(yīng)該繼續(xù)分析下AnimatorSet
其他的一些函數(shù)衣洁,如:cancel()
墓捻、end()
、pause()
坊夫、resume()
甚至setTarget(Object target)
砖第,但是由于這些函數(shù)原則上只是將調(diào)用傳遞至其包含的Animator
,至于一些小的處理細(xì)節(jié)也并沒有太多值得分析的环凿,因此就留待各位自行探索啦梧兼。
整個(gè)Animator
模塊的分析到這,其實(shí)已經(jīng)算比較完整了智听,而且碼了這么多字已經(jīng)開始產(chǎn)生逆反心里羽杰,但是大綱一開始就立好了,如果這時(shí)候放棄總覺得有點(diǎn)蛇尾瞭稼,所以我今天就死磕自己一回忽洛,把屬性動(dòng)畫的幀率決定者Choreographer
擼完。
6. 屬性動(dòng)畫編舞者
Choreographer
的中文翻譯是“編舞者”环肘,我覺得還是很形象的欲虚,所以這一節(jié)的標(biāo)題就直接直譯了。大家對(duì)Choreographer
可能比較陌生悔雹,甚至有可能忘了這家伙在哪出現(xiàn)過(guò)复哆,所以我先來(lái)幫大家回憶下:AnimationHandler
中觸發(fā)定時(shí)任務(wù)的代碼是這樣的:
private void scheduleAnimation() {
if (!mAnimationScheduled) {
// 關(guān)鍵在這 →_→
mChoreographer.postCallback(Choreographer.CALLBACK_ANIMATION, mAnimate, null);
mAnimationScheduled = true;
}
}
這下應(yīng)該回憶起來(lái)了吧!是的腌零,Choreographer
就是任務(wù)分發(fā)的核心梯找,它決定了動(dòng)畫中幀與幀之間的間隔時(shí)長(zhǎng),用人話說(shuō)就是決定了動(dòng)畫的流暢度益涧。
Choreographer
是線程安全的锈锤,其構(gòu)造函數(shù)如下:
private Choreographer(Looper looper) {
// 初始化Handler,用于在創(chuàng)建線程中分發(fā)事件
// 事件通常包括MSG_DO_FRAME、MSG_DO_SCHEDULE_VSYNC久免、MSG_DO_SCHEDULE_CALLBACK三類
mLooper = looper;
mHandler = new FrameHandler(looper);
// 初始化vsync脈沖接收器
mDisplayEventReceiver = USE_VSYNC ? new FrameDisplayEventReceiver(looper) : null;
// 初始化上一幀時(shí)間點(diǎn)
mLastFrameTimeNanos = Long.MIN_VALUE;
// 根據(jù)屏幕刷新頻率計(jì)算幀間隔
mFrameIntervalNanos = (long)(1000000000 / getRefreshRate());
// 創(chuàng)建事件隊(duì)列
mCallbackQueues = new CallbackQueue[CALLBACK_LAST + 1];
for (int i = 0; i <= CALLBACK_LAST; i++) {
mCallbackQueues[i] = new CallbackQueue();
}
}
這里有兩個(gè)關(guān)鍵概念:一個(gè)是VSYNC
浅辙。關(guān)于VSYNC
這個(gè)概念,可參考VSYNC的生成這篇文章阎姥,我們這里可以簡(jiǎn)單地把他理解成屏幕刷新時(shí)的同步信號(hào)记舆,而FrameDisplayEventReceiver
則是在收到同步信號(hào)時(shí)處理一些事件(在Choreographer
中會(huì)向FrameHandler
發(fā)送一個(gè)以FrameDisplayEventReceiver
為callback
的消息,當(dāng)回調(diào)回來(lái)的時(shí)候調(diào)用Choreographer.doFrame
)呼巴;另一個(gè)是CallbackQueue
泽腮,這是一個(gè)事件隊(duì)列,目前包含CALLBACK_INPUT衣赶、CALLBACK_ANIMATION诊赊、CALLBACK_TRAVERSAL、CALLBACK_COMMIT
四種類型的事件隊(duì)列府瞄,這個(gè)隊(duì)列是按照事件觸發(fā)事件排序的優(yōu)先級(jí)隊(duì)列豪筝,以action
+token
作為組合鍵來(lái)判定兩個(gè)事件是否相等,而通常action
分為Runnable
及FrameCallback
兩種摘能,分別由Choreographer.postCallback
和Choreographer.postFrameCallback
向Choreographer
進(jìn)行委派续崖。
這些概念講清楚之后,我們就跟著Animator
中的調(diào)用mChoreographer.postCallback
來(lái)感受一番傳說(shuō)中的編舞者团搞,postCallback
最終會(huì)調(diào)用postCallbackDelayedInternal
來(lái)執(zhí)行具體的邏輯:
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);
}
}
這段代碼首先將事件按callbackType添加至相應(yīng)的事件隊(duì)列严望,然后在指定的時(shí)間點(diǎn)(如無(wú)延時(shí)則直接觸發(fā),有延時(shí)則通過(guò)向FrameHandler
發(fā)送MSG_DO_SCHEDULE_CALLBACK
消息來(lái)進(jìn)行延時(shí)分發(fā))觸發(fā)scheduleFrameLocked
逻恐。(這里厚顏無(wú)恥地打個(gè)小廣告像吻,在向FrameHandler
發(fā)送消息的時(shí)候,將消息設(shè)置成了異步消息复隆,關(guān)于什么是異步消息拨匆,參看我之前分享Handler機(jī)制的文章Android Handler運(yùn)行機(jī)制Java層源碼分析中的分享)
當(dāng)scheduleFrameLocked
被調(diào)用時(shí),做了如下處理:
- 若使用
VSYNC
挽拂,則調(diào)用scheduleVsyncLocked
等待VSYNC
信號(hào)惭每,如上文所述,VSYNC
信號(hào)到來(lái)時(shí)會(huì)通過(guò)向FrameHandler
發(fā)送以FrameDisplayEventReceiver
為callback
的消息來(lái)觸發(fā)doFrame
- 不使用VSYNC亏栈,通過(guò)向
FrameHandler
發(fā)送MSG_DO_FRAME
消息來(lái)觸發(fā)doFrame
台腥,注意這個(gè)消息帶有延時(shí),而延時(shí)的時(shí)長(zhǎng)為上一幀的時(shí)間點(diǎn)加上幀延時(shí)sFrameDelay
(默認(rèn)為10ms)
所以不管是否通過(guò)哪種途徑绒北,最終的歸屬都是doFrame(long frameTimeNanos, int frame)
黎侈,這里主要干了兩件事:
- 調(diào)整
frameTimeNanos
:當(dāng)當(dāng)前時(shí)間與frameTimeNanos
之差大于或等于幀間隔mFrameIntervalNanos
時(shí),調(diào)整frameTimeNanos
確保當(dāng)前時(shí)間與frameTimeNanos
之差小于mFrameIntervalNanos
- 調(diào)用
doCallbacks(int callbackType, long frameTimeNanos)
依次處理mCallbackQueue
中滿足條件的事件闷游,事件隊(duì)列的處理順序?yàn)?code>CALLBACK_INPUT -> CALLBACK_ANIMATION -> CALLBACK_TRAVERSAL -> CALLBACK_COMMIT
事件最終是在doCallbacks(int callbackType, long frameTimeNanos)
中被處理掉的峻汉,拋開細(xì)節(jié)不說(shuō)贴汪,doCallbacks
就是從callbackType
對(duì)應(yīng)的mCallbackQueue
取出處理事件在frameTimeNanos
的事件,然后調(diào)用事件對(duì)應(yīng)action
休吠,實(shí)現(xiàn)事件的處理嘶是。
Choreographer
對(duì)事件的分發(fā)處理流程大致就如上所述,整體上跟Handler的感覺挺像蛛碌,只是因?yàn)楦到y(tǒng)幀頻率關(guān)聯(lián)在一起而有了一些的特殊性,甚至看起來(lái)View的traversal也是通過(guò)它進(jìn)行分發(fā)的辖源,建議有興趣的同學(xué)可以去尋根溯源下蔚携。
7. 后記
分析Animator的代碼對(duì)于作為程序員的我來(lái)說(shuō)其實(shí)并不難,但是碼出這么些字來(lái)其實(shí)還是有點(diǎn)費(fèi)勁了克饶,從中午一直干到晚上酝蜒,差不多八九個(gè)小時(shí),但說(shuō)實(shí)話用文字來(lái)描述這些東西的時(shí)候矾湃,會(huì)逼迫自己去把之前看代碼時(shí)忽略的一些小細(xì)節(jié)也品味了一遍亡脑,寫完之后會(huì)有一種暢通感,就像被打通了任督二脈一樣邀跃,對(duì)Animator的把握變得更加系統(tǒng)和具象霉咨。當(dāng)然,希望這篇分享有給你們帶來(lái)一些啟發(fā)拍屑,也建議各位多用文字把學(xué)到的東西系統(tǒng)地分享出來(lái)途戒,相信我,親測(cè)這絕對(duì)是件利人利己的事僵驰。