Android Animator運行原理詳解

1. 前言

上一篇文章《Android Animation運行原理詳解》介紹了插間動畫的原理,而Android3.0之后引進了一種動畫實現——屬性動畫,放在以前可能會因為要兼容3.0以前系統而小猶豫下,但現在3.0以上系統已占有率已達97%以上(來自Android Studio統計數據),市場上許多應用甚至已經將4.0作為最低兼容版本了,因此屬性動畫基本就是標配了,再說就算真的有老古董應用想要兼容3.0以前系統也可以使用開源庫NineOldAndroids來享受屬性動畫帶來的快感。這種情況下,理解屬性動畫的運行原理,分析屬性動畫相對于插間動畫而言有哪些優勢就比較有意義了,而且屬性動畫似乎已經慢慢取代插間動畫而成為動畫的主流實現方式了。本文主要分享我在學習屬性動畫的過程中的一些收獲,包括:屬性動畫包含哪些基本元素;屬性動畫的運行流程是怎樣的;屬性動畫是怎么對View起作用的;怎么組合并運行多個屬性動畫;屬性動畫的幀率決定者Choreographer是如何工作的。

2. 屬性動畫的基本元素

屬性動畫跟插間動畫一樣會包含動畫相關的屬性,如動畫時長、延遲時間、插間器等等,為了后面分析動畫運行流程時概念更加明確,這里我摘抄了ValueAnimator源碼中的字段,并做了相應的注解,對Animator有過了解的同學可以直接跳過,不熟悉的同學建議先掃一眼,然后重點關注PropertyValuesHolder這個屬性相關的介紹。

// 初始化函數是否被調用
boolean mInitialized = false;

// 動畫時長
private long mDuration = (long)(300 * sDurationScale);
private long mUnscaledDuration = 300;

// 動畫延時
private long mStartDelay = 0;
private long mUnscaledStartDelay = 0;

// 動畫重復模式及次數
private int mRepeatCount = 0;
private int mRepeatMode = RESTART;

// 插間器,參看http://cogitolearning.co.uk/?p=1078
private TimeInterpolator mInterpolator = sDefaultInterpolator;

// 動畫開始運行的時間點
long mStartTime;    
// 是否需要在掉幀的時候調整動畫開始時間點
boolean mStartTimeCommitted;

// 動畫是否反方向運行,當repeatMode=REVERSE是會每個動畫周期反轉一次
private boolean mPlayingBackwards = false;

// 當前動畫在一個動畫周期中所處位置
private float mCurrentFraction = 0f;

// 動畫是否延時
private boolean mStartedDelay = false;
// 動畫完成延時的時間點
private long mDelayStartTime;

// 動畫當前所處的狀態:STOPPED, RUNNING, SEEKED
int mPlayingState = STOPPED;

// 動畫是否被啟動
private boolean mStarted = false;
// 動畫是否被執行(以動畫第一幀被計算為界)
private boolean mRunning = false;

// 回調監聽器
private boolean mStartListenersCalled = false; // 確保AnimatorListener.onAnimationStart(Animator)僅被調用一次
ArrayList<AnimatorListener> mListeners = null; // start,end,cancel,repeat回調
ArrayList<AnimatorPauseListener> mPauseListeners = null; // pause, resume回調
ArrayList<AnimatorUpdateListener> mUpdateListeners = null; // value更新回調

以上屬性都是動畫的基本屬性以及運行過程的狀態記錄,跟插間動畫差別不大,而屬性動畫相對于插間動畫來件引入了一些新的概念:可以暫停和恢復、可以調整進度,這些概念的引入,讓動畫的概念更加飽滿起來,讓動畫有了視頻播放的概念,而跟這些新概率相關的屬性主要包括:

// 動畫是否被暫停
boolean mPaused = false;
// 動畫暫停時間點,用于在動畫被恢復的時候調整mStartTime以確保動畫能優雅地繼續運行
private long mPauseTime;
// 動畫是否從暫停中被恢復,用于表明動畫可以調整mStartTime了
private boolean mResumed = false;

// 動畫被設定的進度位置,具體功效見后文對動畫流程的分析
float mSeekFraction = -1;

除了上面這些動畫屬性以外,Animator還包含了一個有別于Animation的屬性,那就是PropertyValuesHolderPropertyValuesHolder是用來保存某個屬性property對應的一組值,這些值對應了一個動畫周期中的所有關鍵幀。其實,動畫說到底是由動畫幀組成的,Animator可以設定并保存整個動畫周期中的關鍵幀,然后根據這些關鍵幀計算出動畫周期中任一時間點對應的動畫幀的動畫數據,而每一幀的動畫數據里都包含了一個時間點屬性fraction以及一個動畫值mValue,從而實現根據當前的時間點計算當前的動畫值,然后用這個動畫值去更新property對應的屬性,這也是為什么Animator被稱為屬性動畫的原因,因為它的整個動畫過程實際上就是不斷計算并更新對象的屬性:

// 保存property及其values的數組
PropertyValuesHolder[] mValues;
 HashMap<String, PropertyValuesHolder> mValuesMap;

在這里必須要詳細分析下PropertyValuesHolder,因為這關系到后面對動畫流程的理解。注意了,我要出大招了:

PropertyValuesHolder 類圖

上面這張圖詳細地描述了PropertyValuesHolder的組成,我知道大家一般是不愿意看這張圖的,但是為什么我還要放在這?因為我都畫了,不放怎么能顯現出我的工作量- -!但是作為一個正義的程序員,我會用語言描述來拯救你們這些小懶蟲,PropertyValuesHolderPropertyKeyframes組成,其中Property用于描述屬性的特征:如屬性名以及屬性類型,并提供set及get方法用于獲取及設定給定Target的對應屬性值;Keyframes由一組關鍵幀Keyframe組成,每一個關鍵幀由fraction及value來定量描述,于是Keyframes可以根據給定的fraction定位到兩個關鍵幀,這兩個關鍵幀的fraction組成的區間包含給定的fraction,然后根據定位到的兩個關鍵幀以及設定插間器及求值器就可以計算出給定fraction對應的value。于是,PropertyValuesHolder的整個工作流程也就呼之欲出了,首先通過setObjectValues等函數來初始化關鍵幀組mKeyframes,必要的情況下(如ObjectAnimator)可以通過setStartValuesetEndValue來設定第一幀及最末幀的value,以上工作只是完成了PropertyValuesHolder的初始化,之后就可以由Animator在繪制動畫幀的時候通過fraction來調用calculateValue計算該fraction對應的value(實際上是由mKeyframesgetValue方法做出最終計算),獲得對應的value之后,一方面可以通過getAnimatedValue提供給Animator使用,另一方面也可以通過setAnimatedValue方法直接將該值設定到相應Target中去,這樣PropertyValuesHolder的職責也就完成了。有了PropertyValuesHolder的鼎力支持之后,動畫也就可以開始正常的運轉起來了,具體的運轉流程又是怎樣的咧?且聽下節講解。

3. 屬性動畫的運行流程

通過上一小節的介紹,我想各位看客應該對動畫的組成元素有個基本的了解了,接下來就讓我們看看這些基本元素組合在一起之后能誕生怎樣的奇妙功效,讓我們揭開面紗一睹美人芳顏。
屬性動畫的運轉流程大體可分為三種類型:善始善終型、英年早逝型、命運多舛型,但不管哪種類型首先必須進行以下這些必要的初始化工作:

  • 通過setIntValuessetFloatValuessetObjectValuessetValues初始化PropertyValuesHolder數組
  • 設定mDurationmStartDelaymRepeatCountmRepeatModemInterpolator、各種監聽器等動畫相關參數

完成動畫的初始化工作之后,隨著start的調用,動畫正式開始。

(1)善始善終型動畫

善始善終型動畫描述的是一種無病而終的動畫人生,這樣的動畫一旦start就沿著既定的路線一直跑到終點,不快不慢、不長不短。
start函數中,首先會嘗試獲取或創建一個AnimationHandler,這里要是不解釋下AnimationHandler可能就忽悠不下去了,因此我們來看看這是個什么鬼。

protected static ThreadLocal<AnimationHandler> sAnimationHandler =
            new ThreadLocal<AnimationHandler>();

根據官方解釋以及源碼分析可以發現:這家伙就是一個定時任務處理器,根據Choreographer的脈沖周期性地完成指定的任務,由于它是一個線程安全的靜態變量,因此運行在同一線程中的所有Animator共用一個定時任務處理器,這樣的好處在于:一方面可以保證Animator中計算某一時刻動畫幀是在同一線程中運行的,避免了多線程同步的問題;另一方面,該線程下所有動畫共用一個處理器,可以讓這些動畫有效地進行同步,從而讓動畫效果更加優雅。至于AnimationHandler具體用來做哪些任務,我們看看動畫怎么蹦跶的就明白了。
成功獲取到AnimationHandler之后,會做如下處理:

mPlayingState = STOPPED;    //  當前狀態為STOPPED
mStarted = true;    // 動畫啟動標志位置位
mStartedDelay = false;  // 動畫未完成延時標志位復位
mPaused = false;    // 動畫暫停標志位復位

若動畫未設定mStartDelay,還會如下額外操作:

mStartTime = currentTime;   // 動畫起始時間為當前時間
mStartTimeCommitted = true; // 動畫起始時間不可調整
animateValue(fraction); // 計算第一幀動畫值
mRunning = true;    // 動畫運行標志位置位
notifyStartListeners(); // 回調AnimatorListener.onAnimationStart

完成所有啟動操作之后,會將該動畫加入AnimationHandler.mPendingAnimations這個等待列表,接著就正式開啟AnimationHandler的定時任務:

animationHandler.start()
-> animationHandler.scheduleAnimation()
-> mChoreographer.postCallback(Choreographer.CALLBACK_ANIMATION, mAnimate, null);
-> animationHandler.doAnimationFrame(mChoreographer.getFrameTime());

BOSS來襲!!!AnimationHandler.doAnimationFrame就是動畫的Studio,負責動畫生命周期的處理,用下面這個流程圖來簡單描述:

AnimationHandler 流程圖

注意,這個流程圖跟普通的流程圖意義稍微有點區別,那就是整個流程不一定是在一幀內同時完成的,一個動畫可能需要跨越多幀才能從START走到END,比如:動畫可能延遲了3幀才正式開始,然后做了10幀動畫才最后結束。流程清楚了之后,我們來分析下流程中涉及的函數具體的邏輯是怎樣的,但分析之前有必要先說明下AnimationHandler中的保存動畫的幾個列表:

// 保存start被調用且尚未到達第一幀的動畫
protected final ArrayList<ValueAnimator> mPendingAnimations = new ArrayList<ValueAnimator>();

// 保存第一幀已到達且mStartDelay不等于0的動畫,等待調用Animator.delayedAnimationFrame
protected final ArrayList<ValueAnimator> mDelayedAnims = new ArrayList<ValueAnimator>();

// 保存延時已完成的動畫,等待調用Animator.startAnimation
private final ArrayList<ValueAnimator> mReadyAnims = new ArrayList<ValueAnimator>();

// 保存正在運行的動畫,該列表中動畫mRunning為true,等待調用Animator.doAnimationFrame
protected final ArrayList<ValueAnimator> mAnimations = new ArrayList<ValueAnimator>();

// 保存已完成的動畫,等待調用Animator.endAnimation
private final ArrayList<ValueAnimator> mEndingAnims = new ArrayList<ValueAnimator>();

了解了這幾個列表之后,我們就可以描述AnimationHandler.doAnimationFrame做了啥了:

  • mPendingAnimations不為空,則遍歷其內所有Animator做如下處理:
    • mStartDelay==0的Animator,調用Animator.startAnimation,該函數處理如下:
      • 調用Animator.initAnimation,初始化所有PropertyValuesHolder
      • 將該Animator放入AnimationHandler.mAnimations
      • 調用AnimatorListener.onAnimationStart
    • mStartDelay==0的Animator,放入AnimationHandler.mDelayedAnims
  • mDelayedAnims不為空,則遍歷其內所有Animator做如下處理:
    • 調用Animator.delayedAnimationFrame,判斷該動畫延時是否完成,若延時完成:
      • mStartTime = mDelayStartTime + mStartDelay,設定動畫開始時間
      • mStartTimeCommitted = true,禁止調整動畫開始時間
      • mPlayingState = RUNNING,設置動畫運行狀態為RUNNING
      • 將該Animator放入AnimationHandler.mReadyAnims
  • mReadyAnims不為空,則遍歷其內所有Animator做如下處理:
    • 調用Animator.startAnimation
    • 設置Animator.mRunning = true
  • mAnimations不為空,則遍歷其內所有Animator做如下處理:
    • 調用Animator.doAnimationFrame,這個函數是動畫的關鍵處理函數:
      • mPlayingState == STOPPED,則初始化mStartTime為該動畫幀時間
      • 調用animationFrame(long currentTime),做如下處理:
      boolean animationFrame(long currentTime) {
          boolean done = false;
          switch (mPlayingState) {
          case RUNNING:
          case SEEKED:
              // 計算當前動畫幀相對于上一幀動畫而言包含的動畫周期數
              float fraction = mDuration > 0 ? (float)(currentTime - mStartTime) / mDuration : 1f;
              if (mDuration == 0 && mRepeatCount != INFINITE) {
                  // 若動畫周期為0,則可以直接結束動畫
                  mCurrentIteration = mRepeatCount;
                  if (!mReversing) {
                      mPlayingBackwards = false;
                  }
              }
              if (fraction >= 1f) {
                  // 動畫周期數大于1的情況下,需對動畫進行repeat或者結束動畫
                  if (mCurrentIteration < mRepeatCount || mRepeatCount == INFINITE) {
                      // repeat動畫
                      if (mListeners != null) {
                          int numListeners = mListeners.size();
                          for (int i = 0; i < numListeners; ++i) {
                              // 調用AnimationListener.onAnimationRepeat
                              mListeners.get(i).onAnimationRepeat(this);
                          }
                      }
                      if (mRepeatMode == REVERSE) {
                          mPlayingBackwards = !mPlayingBackwards;
                      }
                      // 累加動畫repeat次數
                      mCurrentIteration += (int) fraction;
                      // 調整fraction至[0.0f,1.0f)
                      fraction = fraction % 1f;
                      // 調整動畫開始時間
                      mStartTime += mDuration;
                  } else {
                      // 結束動畫
                      done = true;
                      fraction = Math.min(fraction, 1.0f);
                  }
              }
              if (mPlayingBackwards) {
                  fraction = 1f - fraction;
              }
              // 計算當前動畫幀的動畫數據
              animateValue(fraction);
              break;
          }
      
          return done;
      }
      
        - `animateValue(fraction)`函數如下:
        ```java
        void animateValue(float fraction) {
            fraction = mInterpolator.getInterpolation(fraction);
            mCurrentFraction = fraction;
            int numValues = mValues.length;
            for (int i = 0; i < numValues; ++i) {
                // 通過PropertyValuesHolder計算當前動畫幀的動畫值
                mValues[i].calculateValue(fraction);
            }
            if (mUpdateListeners != null) {
                int numListeners = mUpdateListeners.size();
                for (int i = 0; i < numListeners; ++i) {
                    // 調用AnimatorUpdateListener.onAnimationUpdate,
                    // 在回調中可以通過Animator.getAnimatedValue()獲取當前動畫幀的數據進行最終的動畫處理(如調整Target相應的屬性值)
                    mUpdateListeners.get(i).onAnimationUpdate(this);
                }
            }
        }
  • mEndingAnims不為空,則遍歷其內所有Animator做如下處理:
    • 調用Animator.endAnimation,該函數主要做掃尾工作:
      • 把該動畫從AnimationHandler的所有列表中清除
      • 若未調用過AnimatorListener.onAnimationStart,則調用
      • 調用AnimatorListener.onAnimationEnd
      • 復位動畫所有狀態:如mPlayingState = STOPPEDmRunning=falsemStarted = false等等
  • mAnimationsmDelayedAnims不為空,則對下一幀進行定時。

通過上面這個描述應該能比較清楚的理解動畫的整個流程了,但是這里其實我有一個疑問尚未解惑:在animationFrame(long currentTime)計算得到fraction后,當fraction>=1.0f時會對迭代次數以及動畫開始時間進行調整,代碼如下:

// 計算fraction
fraction = mDuration > 0 ? (float)(currentTime - mStartTime) / mDuration : 1f;

// 調整迭代次數以及動畫開始時間
mCurrentIteration += (int) fraction;
fraction = fraction % 1f;
mStartTime += mDuration;

fraction的計算及調整、mCurrentIteration的累積并不難理解,但是對mStartTime的調整就有點詭異了:無論當前跨越多少個動畫周期,動畫的起始時間只向前調整一個周期,而我查了發現也沒有其他地方會在下一幀動畫運算前對mStartTime再做調整;那么在下一幀動畫中計算fraction時,若上一幀動畫跨越的動畫周期數大于等于2,則這一次計算得到的fraction會比正常跨越周期數多,這意味著動畫實際的運行時長會比理論上的時長短,當然這僅僅發生在當mDuration < 0.5 * frameInterval時,其中frameInterval為兩幀動畫的間隔時長。盡管對于通常僅為16ms的frameInterval來說,這種情形應該算是小概率事件,但是Android為何要如此處理,我想應該是有一個比較合理的理由,只是我目前尚未理解,希望了解緣由的小伙伴能指點一二。

(2)英年早逝型動畫

不是每一個動畫都能正常走完自己的動畫人生,有些動畫可能需要在必要的時候為完成某個絢爛的效果而英勇犧牲。這樣的情況通常有兩種:cancelend。接下我們就一次分析下這兩種情形:
1) Animator.cancel
cancel只會處理那些正在運行或者等待開始運行的動畫,具體的處理邏輯是這樣的:

  • 若未調用過AnimatorListener.onAnimationStart,則調用
  • 調用AnimatorListener.onAnimationCancel
  • 調用Animator.endAnimation
    • 把該動畫從AnimationHandler的所有列表中清除
    • 調用AnimatorListener.onAnimationEnd
    • 復位動畫所有狀態:如mPlayingState = STOPPEDmRunning=falsemStarted = false等等

從上面的邏輯不難發現,cancel對回調的處理是比較完整的,但是cancel被調用之后,動畫的動畫值會停留在當前幀而不會繼續進行計算。
2) Animator.end
end相對于cancel來說有兩個區別:一個是會處理所有動畫;另一個是會計算最末一幀動畫值。其具體的處理邏輯如下所示:

  • 若動畫尚未開始:調用Animatior.startAnimation讓動畫處于正常運行狀態
  • 計算最后一幀動畫的動畫值:animateValue(mPlayingBackwards ? 0f : 1f)
  • 結束動畫:調用endAnimation

這兩種處理都會導致動畫的非正常結束,需要注意的是cancel會保留當前的動畫值,而end會計算最末幀的動畫值。

(3)命運多舛型動畫

之前提到過,屬性動畫相對于插間動畫而言更多的體現出了一種播放視頻的感覺:可以暫停和恢復、可以調整進度,這樣的動畫人生是走走停停的,有時候一不小心還會倒退,過得并不那么一帆風順,是多舛的命運。我們來一一分析一下:
1) pause
pause被調用的時候,僅在動畫已開始(mStarted==true)且當前為非暫停狀態時才進行以下處理:

  • 置位:mPaused = true
  • 調用AnimatorPauseListener.onAnimationPause
  • 復位mResumed = false
  • 清空暫停時間:mPauseTime = -1

做完這些處理之后,就靜靜地等風來(等下一幀動畫的到來),當風來了之后,delayedAnimationFramedoAnimationFrame被調用,此時若仍然處于暫停狀態,就會做如下截擊:

if (mPaused) {
    if (mPauseTime < 0) {
        mPauseTime = frameTime;
    }
    return false;
}

這樣就阻止了動畫的正常運行,并記錄下來動畫暫停的時間,確保恢復之后能讓動畫調整到暫停之前的動畫點正常運行,具體怎么起作用就要看resume這小子的了。

2) resume
resume被調用的時候,僅當當前是暫停狀態時,會做如下處理:

  • 置位:mResumed = true
  • 復位:mPaused = false
  • 調用AnimatorPauseListener.onAnimationResume

當風再次來臨,delayedAnimationFramedoAnimationFrame被調用,此時若處于恢復狀態(mResume==true),就會做如下補償處理:

// delayedAnimationFrame的處理
if (mResumed) {
    mResumed = false;
    if (mPauseTime > 0) {
        mDelayStartTime += (currentTime - mPauseTime);
    }
}

// doAnimationFrame的處理
if (mResumed) {
    mResumed = false;
    if (mPauseTime > 0) {
        mStartTime += (frameTime - mPauseTime);
        mStartTimeCommitted = false;
    }
}

這樣就讓暫停的時間從動畫的運行過程中消除,就好像從來沒暫停過一樣,不帶走一片云彩。

3) seek
我想每個人看電影的時候,或多或少會有這樣的經歷:看到一個特別唯美或者特別感動的畫面,總忍不住想N次回放;又或看到一個無聊或爛俗的橋段,總吐槽著直接跳過。而對動畫進行seek就類似于你拖動電影的進度條,這樣可以讓動畫從動畫過程中的任意一個時刻開始運行,seek是通過setCurrentPlayTime(long playTime)或者setCurrentFraction(float fraction)來實現的,實際上最終的邏輯都在setCurrentFraction里面,這家伙主要干了下面這幾件事:

  • 根據fraction計算并更新mPlayingBackwardsmCurrentIterationmStartTime等動畫關鍵元素
  • 調用animateValue(fraction)計算該fraction對應的動畫值
  • 若動畫為非運行狀態(mPlayingState != RUNNING),則設定mPlayingState = SEEKED

做完這些事,動畫的運行狀態就被強行調整了,當下一幀動畫來臨時,則會從強行設定的這個動畫時間點繼續按正常的動畫流程繼續運行下去,然而,在這里有一個不可忽視的小細節,那就是當用seek向前調整的時候會導致mStartTime先于frameTime,這樣假設還是用mStartTime去調用animationFrame就會導致animationFrame中計算得到的fraction為負值,因此細心的程序員們在調用animationFrame之前做了這樣的處理:Math.max(frameTime, mStartTime),這樣整個世界就清靜了,永遠不會出現負值。

到這為止,動畫的三大運轉流程就講完了,其中有些個處理地比較漂亮的細節可能由于篇幅的問題這里并未提及,希望有心的小伙伴可以再去看看源碼,我想會有不少的收獲的。

4. 屬性動畫與View的結合

前面長篇大論了半天,根本沒有具體講到屬性動畫是怎么在View上起作用的,但其實細致看下來的小伙伴應該很容易推測出怎么用屬性動畫去實現View的變換:那就是根據計算出來的動畫值去修改View的屬性,如alpha、x、y、scaleX、scaleY、translationX、translationY等等,這樣當View重繪時就會產生作用,隨著View連續不斷地被重繪,你的眼中就會產生絢爛多彩的動畫了。這就說完了,是不是感覺這一節也太精簡了,相對于前一節來講簡直讓人難以接受。不要慌,我這么濫情的人是舍不得你們難過的,所以我要把ObjectAnimator這個最常用的家伙祭出來拯救你們受傷的小心臟。
ObjectAnimator是可以在動畫幀計算完成之后直接對Target屬性進行修改的屬性動畫類型,相對于ValueAnimator來說更加省心省力,為了造福廣大Androider,ObjectAnimator默默做了不少工作:

  • 提供setTarget接口,用于設定動畫過程中屬性修改的主體,值得注意的是,若在動畫已啟動的情況下修改Taget會導致當前動畫被cancel,然后等待下一次被start
  • initAnimation會通過調用所有PropertyValuesHoldersetupSetterAndGetter方法實現對Property的set及get方法的初始化,以方便后續對Target對應屬性值的修改
  • 通過setupStartValuesetupEndValues對各PropertyValuesHolder種的首末幀數據的動畫值進行初始化
  • 新增一個mAutoCancel屬性,當mAutoCancel==true時,在start的過程中會清除AnimationHandler中對同一Target及該Target同一屬性進行處理的其他動畫
  • 在動畫的整個過程中,若發現Target不再有效(動畫這種保存的是Target的弱引用),則cancel該動畫
  • 最后,在animationValue函數中,調用PropertyValuesHolder.setAnimatedValueTarget的屬性進行修改。

以上,就是ObjectAnimator背著ValueAnimator額外做的各種“勾當”,順帶再補充個小細節,在Animator中保存PropertyValuesHolder的是一個數組,而在函數animationValue中會遍歷處理所有的PropertyValuesHolder,因此一個動畫實現多個屬性的同時修改是一件非常容易的事。雖然知道了怎么實現一個動畫中修改多個屬性,但是怎么實現多個動畫的組合運行還尚未可知,我們在下一節里揭秘所有內幕。

5. 屬性動畫的組合運行

舉個例子來說明組合運行多個屬性動畫的意思:我想在平移一個View的同時改變這個View的透明度,平移完成之后我需要放大整個View。看過《Android Animation運行原理詳解》這篇文章的同學應該知道插間動畫是可以實現組合運行多個動畫的,但是其實現上只支持“在某個動畫的同時做另外一個動畫”這種“playTogether”的模式。然后到了屬性動畫,我們會驚喜的發現組合動畫AnimatorSet除了可以實現“playTogether”模式(下文中“with”模式與此同義)之外,還支持“before”及“after”模式,這家伙簡直是吃了竄天猴了——想上天啊,但是不得不說,我喜歡23333~~
為了支持這些組合模式,AnimatorSet下了血本,引入了Dependency以及Node這兩個數據結構,其中Dependency表示一條依賴規則:

    private static class Dependency {
        // 規則類型
        public int rule;
        static final int WITH = 0;
        static final int AFTER = 1;
        
        // 規則綁定的主體
        public Node node;
    }

Node用于在AnimatorSet表征其包含的Animator,其數據結構如下:

    private static class Node implements Cloneable {
        // 對應的動畫
        public Animator animation;
        
        //  該Node包含的規則列表
        public ArrayList<Dependency> dependencies = null;
        // dependencies的副本,應用在動畫運行過程,避免損壞原始數據
        public ArrayList<Dependency> tmpDependencies = null;
        
        // 該節點依賴的節點
        public ArrayList<Node> nodeDependencies = null;
        
        // 依賴該節點的節點
        public ArrayList<Node> nodeDependents = null;
        
        // 該節點對應的動畫是否完成
        public boolean done = false;
    }

而在AnimatorSet中是這樣來存儲Node:

    // 利用一個list及map來冗余地存儲該AnimatorSet包含的所有Node
    private ArrayMap<Animator, Node> mNodeMap = new ArrayMap<Animator, Node>();
    private ArrayList<Node> mNodes = new ArrayList<Node>();
    
    // 用于存儲按動畫運行順序排好序的所有Node
    private ArrayList<Node> mSortedNodes = new ArrayList<Node>();

有了這兩個利器之后,我們再來討論三種組合模式的實現就變得簡單了,這三種組合模式都是通過一個叫Builder的家伙來創建的,可以通過AnimatiorSet.play(Animator)來創建Builder,這也說明每一個Builder都會有一個規則主體Animator,而用這個Builder創建的規則都是以這個主體Animator為基準的,這也意味著該Builder下多條規則之間是沒有直接必然的關聯的,但是規則之間可能會因為主體Animator而產生間接的關系,這個時候應該舉個例子來說明下這段抽象的描述,但是舉例之前必須先分析三種組合模式的具體實現:

    // 根據Animator獲取或創建Node
    Node node = mNodeMap.get(anim);
    if (node == null) {
        node = new Node(anim);
        mNodeMap.put(anim, node);
        mNodes.add(node);
    }
    
    // with模式實現
    Dependency dependency = new Dependency(mCurrentNode, Dependency.WITH);
    node.addDependency(dependency);
    
    // before模式實現
    Dependency dependency = new Dependency(mCurrentNode, Dependency.AFTER);
    node.addDependency(dependency);  
    
    // after模式實現
    Dependency dependency = new Dependency(node, Dependency.AFTER);
    mCurrentNode.addDependency(dependency);

從實現中可以看出在創建規則的時候實際上我們定義并記錄了Node之間的相互關系,同時我們發現由于在Dependency并未定義“before”類型的規則,因此“before”模式實際是用“after”模式來間接實現的。分析完這三種組合模式的具體實現之后,就可以繼續前面的舉例了:

AnimatorSet s = new AnimatorSet();

// 下面代碼產生的規則并不能確定anim2與anim3的先后關系
s.play(anim1).before(anim2).before(anim3);

// 下面代碼產生的規則可間接確定anim2與anim3的先后關系
s.play(anim1).before(anim2).after(anim3);

// 下面代碼產生的規則可完全確定anim1、anim2、anim3之間的先后關系
s.play(anim1).before(anim2);
s.play(anim2).before(anim3);

這回應該把之前那段抽象的描述解釋清楚了,但是另外一個懸念不知道各位有沒有發現:我們在創建規則的時候只是記錄了Node之間的相互關系,但是這種相互關系具體是怎么起作用的尚未可知,真相就蘊藏在AnimatorSet對其包含的動畫的調度過程中,說曹操曹操到,下面我們就來分析AnimatorSet是怎么管理這么多動畫小朋友的,要理清楚其中奧妙,還不得不提到兩個特殊的監聽器:
1) DependencyListener implements AnimatorListener
DependencyListener用于具化依賴規則:

    public void onAnimationEnd(Animator animation) {
        if (mRule == Dependency.AFTER) {
            startIfReady(animation);
        }
    }

    public void onAnimationStart(Animator animation) {
        if (mRule == Dependency.WITH) {
            startIfReady(animation);
        }
    }

當動畫開始或結束時,會分析以動畫對應Node(設為NodeA)為依賴的Node(設為NodeB),若將NodeA從NodeB的tmpDependencies中移除之后tmpDependencies不在包含其他Node則說明NodeB的啟動條件已滿足。

2) AnimatorSetListener implements AnimatorListener
AnimatorSetListener對于整個AnimatorSet來說僅有一個實例,該實例會被設定到所有被包含的Animator中去,用于管理AnimatorSet的回調,如:僅當所有Animator均結束之后,才調用AnimatorSet監聽器的onAnimationEnd;確保cancel時對每一Animator僅調用一次onAnimationCancel

了解了這兩個監聽器之后,我們就可以以AnimatorSet.start為切入點一氣呵成地理解AnimatorSet管理所有Animator的邏輯,當AnimatorSet.start函數被調用時AnimatorSet被正式激活:

  • 根據AnimatorSet參數初始化包含的Animator

    • 禁用所有Animator的異步模式
    • mDuration >= 0,則將該mDuration設定至所有Animator
    • mInterpolator != null,則將該mInterpolator設定至所有Animator
  • 調用sortNodes函數根據Node之間的依賴規則確定Node中動畫觸發的先后順序,存儲在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,作為可直接啟動的Node放入nodesToStart
    • 對于不可直接啟動的Node,針對其每一條依賴規則創建一個DependencyListener加入其監聽器列表
    • AnimatorSetListener加入所有Node的監聽器列表
  • 根據該AnimatorSet是否需要延時分別處理:

    • 需要延時:創建一個輔助延時Animator,設定其mDurationAnimatorSet的延時時長,并在延時Animator結束之后啟動nodesToStart中的所有動畫
    • 無需延時:直接啟動nodesToStart中的所有動畫
  • 調用AnimatorSet中設定的監聽器的onAnimationStart

  • AnimatorSet不包含任何Animator(即mNodes為空)且無需延時,則直接結束該AnimatorSet,并調用AnimatorSet中設定的監聽器的onAnimationEnd

上面這一段話請在理解了上文中提到的兩個特殊監聽器之后再閱讀,這樣你才能更加清楚的理解為什么在start函數中這樣處理完了之后就可以實現根據Node之間既定的依賴關系有序的完成所有動畫。按常理,應該繼續分析下AnimatorSet其他的一些函數,如:cancel()end()pause()resume()甚至setTarget(Object target),但是由于這些函數原則上只是將調用傳遞至其包含的Animator,至于一些小的處理細節也并沒有太多值得分析的,因此就留待各位自行探索啦。
整個Animator模塊的分析到這,其實已經算比較完整了,而且碼了這么多字已經開始產生逆反心里,但是大綱一開始就立好了,如果這時候放棄總覺得有點蛇尾,所以我今天就死磕自己一回,把屬性動畫的幀率決定者Choreographer擼完。

6. 屬性動畫編舞者

Choreographer的中文翻譯是“編舞者”,我覺得還是很形象的,所以這一節的標題就直接直譯了。大家對Choreographer可能比較陌生,甚至有可能忘了這家伙在哪出現過,所以我先來幫大家回憶下:AnimationHandler中觸發定時任務的代碼是這樣的:

    private void scheduleAnimation() {
        if (!mAnimationScheduled) {
            // 關鍵在這 →_→
            mChoreographer.postCallback(Choreographer.CALLBACK_ANIMATION, mAnimate, null);
            mAnimationScheduled = true;
        }
    }

這下應該回憶起來了吧!是的,Choreographer就是任務分發的核心,它決定了動畫中幀與幀之間的間隔時長,用人話說就是決定了動畫的流暢度。
Choreographer是線程安全的,其構造函數如下:

    private Choreographer(Looper looper) {
        // 初始化Handler,用于在創建線程中分發事件
        // 事件通常包括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;
        
        // 初始化上一幀時間點
        mLastFrameTimeNanos = Long.MIN_VALUE;
        
        // 根據屏幕刷新頻率計算幀間隔
        mFrameIntervalNanos = (long)(1000000000 / getRefreshRate());
        
        // 創建事件隊列
        mCallbackQueues = new CallbackQueue[CALLBACK_LAST + 1];
        for (int i = 0; i <= CALLBACK_LAST; i++) {
            mCallbackQueues[i] = new CallbackQueue();
        }
    }

這里有兩個關鍵概念:一個是VSYNC。關于VSYNC這個概念,可參考VSYNC的生成這篇文章,我們這里可以簡單地把他理解成屏幕刷新時的同步信號,而FrameDisplayEventReceiver則是在收到同步信號時處理一些事件(在Choreographer中會向FrameHandler發送一個以FrameDisplayEventReceivercallback的消息,當回調回來的時候調用Choreographer.doFrame);另一個是CallbackQueue,這是一個事件隊列,目前包含CALLBACK_INPUT、CALLBACK_ANIMATION、CALLBACK_TRAVERSAL、CALLBACK_COMMIT四種類型的事件隊列,這個隊列是按照事件觸發事件排序的優先級隊列,以action+token作為組合鍵來判定兩個事件是否相等,而通常action分為RunnableFrameCallback兩種,分別由Choreographer.postCallbackChoreographer.postFrameCallbackChoreographer進行委派。
這些概念講清楚之后,我們就跟著Animator中的調用mChoreographer.postCallback來感受一番傳說中的編舞者,postCallback最終會調用postCallbackDelayedInternal來執行具體的邏輯:

    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添加至相應的事件隊列,然后在指定的時間點(如無延時則直接觸發,有延時則通過向FrameHandler發送MSG_DO_SCHEDULE_CALLBACK消息來進行延時分發)觸發scheduleFrameLocked。(這里厚顏無恥地打個小廣告,在向FrameHandler發送消息的時候,將消息設置成了異步消息,關于什么是異步消息,參看我之前分享Handler機制的文章Android Handler運行機制Java層源碼分析中的分享)

scheduleFrameLocked被調用時,做了如下處理:

  • 若使用VSYNC,則調用scheduleVsyncLocked等待VSYNC信號,如上文所述,VSYNC信號到來時會通過向FrameHandler發送以FrameDisplayEventReceivercallback的消息來觸發doFrame
  • 不使用VSYNC,通過向FrameHandler發送MSG_DO_FRAME消息來觸發doFrame,注意這個消息帶有延時,而延時的時長為上一幀的時間點加上幀延時sFrameDelay(默認為10ms)

所以不管是否通過哪種途徑,最終的歸屬都是doFrame(long frameTimeNanos, int frame),這里主要干了兩件事:

  • 調整frameTimeNanos:當當前時間與frameTimeNanos之差大于或等于幀間隔mFrameIntervalNanos時,調整frameTimeNanos確保當前時間與frameTimeNanos之差小于mFrameIntervalNanos
  • 調用doCallbacks(int callbackType, long frameTimeNanos)依次處理mCallbackQueue中滿足條件的事件,事件隊列的處理順序為CALLBACK_INPUT -> CALLBACK_ANIMATION -> CALLBACK_TRAVERSAL -> CALLBACK_COMMIT

事件最終是在doCallbacks(int callbackType, long frameTimeNanos)中被處理掉的,拋開細節不說,doCallbacks就是從callbackType對應的mCallbackQueue取出處理事件在frameTimeNanos的事件,然后調用事件對應action,實現事件的處理。
Choreographer對事件的分發處理流程大致就如上所述,整體上跟Handler的感覺挺像,只是因為跟系統幀頻率關聯在一起而有了一些的特殊性,甚至看起來View的traversal也是通過它進行分發的,建議有興趣的同學可以去尋根溯源下。

7. 后記

分析Animator的代碼對于作為程序員的我來說其實并不難,但是碼出這么些字來其實還是有點費勁了,從中午一直干到晚上,差不多八九個小時,但說實話用文字來描述這些東西的時候,會逼迫自己去把之前看代碼時忽略的一些小細節也品味了一遍,寫完之后會有一種暢通感,就像被打通了任督二脈一樣,對Animator的把握變得更加系統和具象。當然,希望這篇分享有給你們帶來一些啟發,也建議各位多用文字把學到的東西系統地分享出來,相信我,親測這絕對是件利人利己的事。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • Animation Animation類是所有動畫(scale、alpha、translate、rotate)的基...
    四月一號閱讀 1,934評論 0 10
  • 1 背景 不能只分析源碼呀,分析的同時也要整理歸納基礎知識,剛好有人微博私信讓全面說說Android的動畫,所以今...
    未聞椛洺閱讀 2,735評論 0 10
  • Android框架提供了兩種類型的動畫:View Animation(也稱視圖動畫)和Property Anima...
    RxCode閱讀 1,680評論 1 5
  • 提到拆書,就必定要提到趙周老師,要提到拆書幫。 趙周老師寫了一本名為《這樣讀書就夠了》的書籍,書中提到了“拆書”、...
    墨竹_sunshine閱讀 616評論 4 12
  • 如何做好淘寶SEO?不管新手還是老手做店鋪時,都會面臨死款這種尷尬的局面,那么該如何才能挽救店鋪的死款呢?怎么讓它...
    b6f19acf7f09閱讀 358評論 0 0