Android Animator運(yùn)行原理詳解

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)畫流程的理解助币。注意了,我要出大招了:

PropertyValuesHolder 類圖

上面這張圖詳細(xì)地描述了PropertyValuesHolder的組成螟碎,我知道大家一般是不愿意看這張圖的眉菱,但是為什么我還要放在這?因?yàn)槲叶籍嬃说舴郑环旁趺茨茱@現(xiàn)出我的工作量- -俭缓!但是作為一個(gè)正義的程序員,我會(huì)用語(yǔ)言描述來(lái)拯救你們這些小懶蟲叉抡,PropertyValuesHolderPropertyKeyframes組成尔崔,其中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ò)setStartValuesetEndValue來(lái)設(shè)定第一幀及最末幀的value倡勇,以上工作只是完成了PropertyValuesHolder的初始化逞刷,之后就可以由Animator在繪制動(dòng)畫幀的時(shí)候通過(guò)fraction來(lái)調(diào)用calculateValue計(jì)算該fraction對(duì)應(yīng)的value(實(shí)際上是由mKeyframesgetValue方法做出最終計(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ò)setIntValuessetFloatValues洋丐、setObjectValuessetValues初始化PropertyValuesHolder數(shù)組
  • 設(shè)定mDurationmStartDelay挥等、mRepeatCount友绝、mRepeatModemInterpolator肝劲、各種監(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)單描述:

AnimationHandler 流程圖

注意,這個(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
    • 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
  • mReadyAnims不為空揽碘,則遍歷其內(nèi)所有Animator做如下處理:
    • 調(diào)用Animator.startAnimation
    • 設(shè)置Animator.mRunning = true
  • 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;
      }
      
        - `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=falsemStarted = false等等
  • mAnimationsmDelayedAnims不為空宿稀,則對(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è)絢爛的效果而英勇犧牲预烙。這樣的情況通常有兩種:cancelend。接下我們就一次分析下這兩種情形:
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=falsemStarted = false等等

從上面的邏輯不難發(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)了之后化借,delayedAnimationFramedoAnimationFrame被調(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)臨,delayedAnimationFramedoAnimationFrame被調(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嚷兔、mCurrentIterationmStartTime等動(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)用所有PropertyValuesHoldersetupSetterAndGetter方法實(shí)現(xiàn)對(duì)Property的set及get方法的初始化吊骤,以方便后續(xù)對(duì)Target對(duì)應(yīng)屬性值的修改
  • 通過(guò)setupStartValuesetupEndValues對(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
  • 分析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è)定其mDurationAnimatorSet的延時(shí)時(shí)長(zhǎng)比勉,并在延時(shí)Animator結(jié)束之后啟動(dòng)nodesToStart中的所有動(dòng)畫
    • 無(wú)需延時(shí):直接啟動(dòng)nodesToStart中的所有動(dòng)畫
  • 調(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è)以FrameDisplayEventReceivercallback的消息,當(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分為RunnableFrameCallback兩種摘能,分別由Choreographer.postCallbackChoreographer.postFrameCallbackChoreographer進(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ā)送以FrameDisplayEventReceivercallback的消息來(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ì)是件利人利己的事僵驰。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末喷斋,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子蒜茴,更是在濱河造成了極大的恐慌星爪,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,402評(píng)論 6 499
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件粉私,死亡現(xiàn)場(chǎng)離奇詭異顽腾,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)诺核,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,377評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門崔泵,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人猪瞬,你說(shuō)我怎么就攤上這事憎瘸。” “怎么了陈瘦?”我有些...
    開封第一講書人閱讀 162,483評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵幌甘,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我,道長(zhǎng)锅风,這世上最難降的妖魔是什么酥诽? 我笑而不...
    開封第一講書人閱讀 58,165評(píng)論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮皱埠,結(jié)果婚禮上肮帐,老公的妹妹穿的比我還像新娘。我一直安慰自己边器,他們只是感情好训枢,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,176評(píng)論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著忘巧,像睡著了一般恒界。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上砚嘴,一...
    開封第一講書人閱讀 51,146評(píng)論 1 297
  • 那天十酣,我揣著相機(jī)與錄音,去河邊找鬼际长。 笑死耸采,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的工育。 我是一名探鬼主播洋幻,決...
    沈念sama閱讀 40,032評(píng)論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼翅娶!你這毒婦竟也來(lái)了文留?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,896評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤竭沫,失蹤者是張志新(化名)和其女友劉穎燥翅,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體蜕提,經(jīng)...
    沈念sama閱讀 45,311評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡森书,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,536評(píng)論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了谎势。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片凛膏。...
    茶點(diǎn)故事閱讀 39,696評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖脏榆,靈堂內(nèi)的尸體忽然破棺而出猖毫,到底是詐尸還是另有隱情,我是刑警寧澤须喂,帶...
    沈念sama閱讀 35,413評(píng)論 5 343
  • 正文 年R本政府宣布吁断,位于F島的核電站趁蕊,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏仔役。R本人自食惡果不足惜掷伙,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,008評(píng)論 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望又兵。 院中可真熱鬧任柜,春花似錦、人聲如沸沛厨。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)俄烁。三九已至,卻和暖如春级野,著一層夾襖步出監(jiān)牢的瞬間页屠,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,815評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工蓖柔, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留辰企,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,698評(píng)論 2 368
  • 正文 我出身青樓况鸣,卻偏偏與公主長(zhǎng)得像牢贸,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子镐捧,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,592評(píng)論 2 353

推薦閱讀更多精彩內(nèi)容

  • Animation Animation類是所有動(dòng)畫(scale潜索、alpha、translate懂酱、rotate)的基...
    四月一號(hào)閱讀 1,915評(píng)論 0 10
  • 1 背景 不能只分析源碼呀竹习,分析的同時(shí)也要整理歸納基礎(chǔ)知識(shí),剛好有人微博私信讓全面說(shuō)說(shuō)Android的動(dòng)畫列牺,所以今...
    未聞椛洺閱讀 2,705評(píng)論 0 10
  • Android框架提供了兩種類型的動(dòng)畫:View Animation(也稱視圖動(dòng)畫)和Property Anima...
    RxCode閱讀 1,630評(píng)論 1 5
  • 提到拆書整陌,就必定要提到趙周老師,要提到拆書幫瞎领。 趙周老師寫了一本名為《這樣讀書就夠了》的書籍泌辫,書中提到了“拆書”、...
    墨竹_sunshine閱讀 591評(píng)論 4 12
  • 如何做好淘寶SEO?不管新手還是老手做店鋪時(shí)驼修,都會(huì)面臨死款這種尷尬的局面澜搅,那么該如何才能挽救店鋪的死款呢?怎么讓它...
    b6f19acf7f09閱讀 352評(píng)論 0 0