1. 程式人生 > >動畫原始碼解析

動畫原始碼解析

目錄介紹

  • 1.Animation和Animator區別
  • 2.Animation執行原理和原始碼分析
    • 2.1 基本屬性介紹
    • 2.2 如何計算動畫資料
    • 2.3 什麼是動畫更新函式
    • 2.4 動畫資料如何儲存
    • 2.5 Animation的呼叫
  • 3.Animator執行原理和原始碼分析
    • 3.1 屬性動畫的基本屬性
    • 3.2 屬性動畫新的概念
    • 3.3 PropertyValuesHolder作用
    • 3.4 屬性動畫start執行流程
    • 3.5 屬性動畫cancel和end執行流程
    • 3.6 屬性動畫pase和resume執行流程
    • 3.7 屬性動畫與View結合

好訊息

  • 部落格筆記大彙總【16年3月到至今】,包括Java基礎及深入知識點,Android技術部落格,Python學習筆記等等,還包括平時開發中遇到的bug彙總,當然也在工作之餘收集了大量的面試題,長期更新維護並且修正,持續完善……開源的檔案是markdown格式的!同時也開源了生活部落格,從12年起,積累共計47篇[近20萬字],轉載請註明出處,謝謝!
  • 如果覺得好,可以star一下,謝謝!當然也歡迎提出建議,萬事起於忽微,量變引起質變!

1.Animation和Animator區別

  • 對於 Animation 動畫:
    • 實現機制是,在每次進行繪圖的時候,通過對整塊畫布的矩陣進行變換,從而實現一種檢視座標的移動,但實際上其在 View內部真實的座標位置及其他相關屬性始終恆定.
  • 對於 Animator 動畫:
    • Animator動畫的實現機制說起來其實更加簡單一點,因為他其實只是計算動畫開啟之後,結束之前,到某個時間點得時候,某個屬性應該有的值,然後通過回撥介面去設定具體值,其實 Animator 內部並沒有針對某個 view 進行重新整理,來實現動畫的行為,動畫的實現是在設定具體值的時候,方法內部自行調取的類似 invalidate 之類的方法實現的.也就是說,使用 Animator ,內部的屬性發生了變化
  • 或者更簡單一點說
    • 前者屬性動畫,改變控制元件屬性,(比如平移以後點選有事件觸發)
    • 後者補間動畫,只產生動畫效果(平移之後點無事件觸發,前提是你fillafter=true)

2.Animation執行原理和原始碼分析

2.1 基本屬性介紹

  • 上一篇文章已經對補間動畫做了詳細的說明,不過這裡還是需要重複說一下動畫屬性的作用
    • mStartTime:動畫實際開始時間
    • mStartOffset:動畫延遲時間
    • mFillEnabled:mFillBefore及mFillAfter是否使能
    • mFillBefore:動畫結束之後是否需要進行應用動畫
    • mFillAfter:動畫開始之前是否需要進行應用動畫
    • mDuration:單次動畫執行時長
    • mRepeatMode:動畫重複模式(RESTART、REVERSE)
    • mRepeatCount:動畫重複次數(INFINITE,直接值)
    • mInterceptor:動畫插間器
    • mBackgroundColor:動畫背景顏色
    • mListener:動畫開始、結束、重複回撥監聽器

2.2 如何計算動畫資料

  • 首先進入Animation類,然後找到getTransformation方法,主要是分析這個方法邏輯,如圖所示
    • image
  • 那麼這個方法中做了什麼呢?Animation在其getTransformation函式被呼叫時會計算一幀動畫資料,而上面這些屬性基本都是在計算動畫資料時有相關的作用。
  • 第一步:若startTime為START_ON_FIRST_FRAME(值為-1)時,將startTime設定為curTime
  • 第二步:計算當前動畫進度:
    • normalizedTime = (curTime - (startTime + startOffset))/duration
    • 若mFillEnabled==false:將normalisedTime夾逼至[0.0f, 1.0f]
  • 第三步:判斷是否需要計算動畫資料:
    • 若normalisedTime在[0.0f, 1.0f],需計算動畫資料
    • 若normalisedTime不在[0.0f, 1.0f]:
      • normalisedTime<0.0f, 僅當mFillBefore==true時才計算動畫資料
      • normalisedTime>1.0f, 僅當mFillAfter==true時才計算動畫資料
  • 第四步:若需需要計算動畫資料:
    • 若當前為第一幀動畫,觸發mListener.onAnimationStart
    • 若mFillEnabled==false:將normalisedTime夾逼至[0.0f, 1.0f]
    • 根據插間器mInterpolator調整動畫進度:
    • interpolatedTime = mInterpolator.getInterpolation(normalizedTime)
    • 若動畫反轉標誌位mCycleFlip為true,則
    • interpolatedTime = 1.0 - normalizedTime
    • 呼叫動畫更新函式applyTransformation(interpolatedTime, transformation)計算出動畫資料
  • 第五步:若夾逼之前normalisedTime大於1.0f, 則判斷是否需繼續執行動畫:
    • 已執行次數mRepeatCount等於需執行次數mRepeated
      • 若未觸發mListener.onAnimationEnd,則觸發之
    • 已執行次數mRepeatCount不等於需執行次數mRepeated
      • 自增mRepeatCount
      • 重置mStartTime為-1
      • 若mRepeatMode為REVERSE,則取反mCycleFlip
      • 觸發mListener.onAnimationRepeat

2.3 什麼是動畫更新函式

  • 下面我們來看一下getTransformation方法中的這一行程式碼applyTransformation(interpolatedTime, outTransformation),然後進去看看這個方法。如下所示
    • image
  • 這個方法的用途是幹啥呢?從這個英文解釋中可以得知:getTransform的助手。子類應該實現這一點,以應用給定的內插值來應用它們的轉換。該方法的實現應該總是替換指定的轉換或文件,而不是這樣做的。
  • 都知道Animation是個抽象類,接著我們這些逗比程式設計師可以看看它的某一個子類,比如看看ScaleAnimation中的applyTransformation方法吧。
    • 是否設定縮放中心點:
      • 若mPivotX0 且 mPivotY0:transformation.getMatrix().setScale(sx, sy)
      • 否則:transformation.getMatrix().setScale(sx, sy, mPivotX, mPivotY)
    • image
  • 介紹到這裡還是沒有講明白它的具體作用,它是在什麼情況下呼叫的。不要著急,接下來會慢慢分析的……

2.4 動畫資料如何儲存

  • 可以看到applyTransformation(float interpolatedTime, Transformation t)這個方法中帶有一個Transformation引數,那麼這個引數是幹啥呢?
    • 實際上,Animation的動畫函式getTransformation目的在於生成當前幀的一個Transformation,這個Transformation採用alpha以及Matrix儲存了一幀動畫的資料,Transformation包含兩種模式:
      • alpha模式:用於支援透明度動畫
      • matrix模式:用於支援縮放、平移以及旋轉動畫
    • 同時,Transformation還提供了許多兩個介面用於組合多個Transformation:
      • compose:前結合(alpha相乘、矩陣右乘、邊界疊加)
      • postCompose:後結合(alpha相乘、矩陣左乘、邊界疊加

2.5 Animation的呼叫

  • getTransformation這個函式究竟是在哪裡呼叫的?計算得到的動畫資料又是怎麼被應用的?為什麼Animation這個包要放在android.view下面以及Animation完成之後為什麼View本身的屬性不會被改變。慢慢看……
    • 要了解Animation,先從要從Animation的基本使用View.startAnimation開始尋根溯源:如下所示
    • image
  • 接著看看setStartTime這個方法,主要是設定一些屬性。
    • image
  • 接著看看setAnimation(animation)方法原始碼
    • 設定要為此檢視播放的下一個動畫。如果希望動畫立即播放,請使用{@link#startAnimation(android.view.animation.Animation)}代替此方法,該方法允許對啟動時間和無效時間進行細粒度控制,但必須確保動畫具有啟動時間集,並且當動畫應該啟動時,檢視的父檢視(控制子檢視上的動畫)將失效。
    public void setAnimation(Animation animation) {
        mCurrentAnimation = animation;
        if (animation != null) {
            if (mAttachInfo != null && mAttachInfo.mDisplayState == Display.STATE_OFF
                    && animation.getStartTime() == Animation.START_ON_FIRST_FRAME) {
                animation.setStartTime(AnimationUtils.currentAnimationTimeMillis());
            }
            animation.reset();
        }
    }
    
  • 接著重點看一下invalidate(true)這個方法
    • 通過invalidate(true)函式會觸發View的重新繪製,那麼在View.draw是怎麼走到對Animation的處理函式呢?
    View.draw(Canvas)
    —> ViewGroup.dispatchDraw(Canvas)
    —> ViewGroup.drawChild(Canvas, View, long)
    —> View.draw(Canvas, ViewGroup, long)
    —> View.applyLegacyAnimation(ViewGroup, long, Animation, boolean)
    
    • image
  • 接著看看View中applyLegacyAnimation這個方法
    private boolean applyLegacyAnimation(ViewGroup parent, long drawingTime,
            Animation a, boolean scalingRequired) {
        Transformation invalidationTransform;
        final int flags = parent.mGroupFlags;
        //判斷Animation是否初始化
        final boolean initialized = a.isInitialized();
        //如果沒有初始化,則進行初始化
        if (!initialized) {
            a.initialize(mRight - mLeft, mBottom - mTop, parent.getWidth(), parent.getHeight());
            a.initializeInvalidateRegion(0, 0, mRight - mLeft, mBottom - mTop);
            if (mAttachInfo != null) a.setListenerHandler(mAttachInfo.mHandler);
            //由父檢視組呼叫,通知當前與此檢視關聯的動畫的開始。如果重寫此方法,則始終呼叫Super.on動畫Start();
            onAnimationStart();
        }
    
        //獲取Transformation物件
        final Transformation t = parent.getChildTransformation();
        //獲取要在指定時間點應用的轉換,這個方法最終呼叫了Animation中的getTransformation方法
        //呼叫getTransformation根據當前繪製事件生成Animation中對應幀的動畫資料
        boolean more = a.getTransformation(drawingTime, t, 1f);
        if (scalingRequired && mAttachInfo.mApplicationScale != 1f) {
            if (parent.mInvalidationTransformation == null) {
                parent.mInvalidationTransformation = new Transformation();
            }
            invalidationTransform = parent.mInvalidationTransformation;
            a.getTransformation(drawingTime, invalidationTransform, 1f);
        } else {
            invalidationTransform = t;
        }
    
        //下面主要是,根據動畫資料設定重繪製區域
        if (more) {
            if (!a.willChangeBounds()) {
                if ((flags & (ViewGroup.FLAG_OPTIMIZE_INVALIDATE | ViewGroup.FLAG_ANIMATION_DONE)) ==
                        ViewGroup.FLAG_OPTIMIZE_INVALIDATE) {
                    parent.mGroupFlags |= ViewGroup.FLAG_INVALIDATE_REQUIRED;
                } else if ((flags & ViewGroup.FLAG_INVALIDATE_REQUIRED) == 0) {
                    parent.mPrivateFlags |= PFLAG_DRAW_ANIMATION;
                    //呼叫ViewGroup.invalidate(int l, int t, int r, int b)設定繪製區域
                    parent.invalidate(mLeft, mTop, mRight, mBottom);
                }
            } else {
                if (parent.mInvalidateRegion == null) {
                    parent.mInvalidateRegion = new RectF();
                }
                final RectF region = parent.mInvalidateRegion;
                a.getInvalidateRegion(0, 0, mRight - mLeft, mBottom - mTop, region,
                        invalidationTransform);
                parent.mPrivateFlags |= PFLAG_DRAW_ANIMATION;
    
                final int left = mLeft + (int) region.left;
                final int top = mTop + (int) region.top;
                
                //呼叫ViewGroup.invalidate(int l, int t, int r, int b)設定繪製區域
                parent.invalidate(left, top, left + (int) (region.width() + .5f),
                        top + (int) (region.height() + .5f));
            }
        }
        return more;
    }
    
    • View.applyLegacyAnimation就是Animation大顯神通的舞臺,其核心程式碼主要分三個部分
      • 初始化Animation(僅初始化一次)
        • 呼叫Animation.initialize(width, height, parentWidth, parentHeight),通過View及ParentView的Size來解析Animation中的相關資料;
        • 呼叫Animation.initializeInvalidateRegion(left, top, right, bottom)來設定動畫的初始區域,並在fillBefore為true時計算Animation動畫進度為0.0f的資料
      • 呼叫getTransformation根據當前繪製事件生成Animation中對應幀的動畫資料
      • 根據動畫資料設定重繪製區域
        • 若僅為Alpha動畫,此時動畫區域為View的當前區域,且不會產生變化
        • 若包含非Alpha動畫,此時動畫區域需要呼叫Animation.getInvalidateRegion進行計算,該函式會根據上述生成動畫資料Thransformation中的Matrix進行計算,並與之前的動畫區域執行unio操作,從而獲取動畫的完整區域
        • 呼叫ViewGroup.invalidate(int l, int t, int r, int b)設定繪製區域
  • 當View.applyLegacyAnimation呼叫完成之後,View此次繪製的動畫資料就構建完成,之後便回到View.draw(Canvas, ViewGroup, long)應用動畫資料對檢視進行繪製重新整理,如下所示:
    • image
    • 重點看到Animation產生的動畫資料實際並不是應用在View本身的,而是應用在RenderNode或者Canvas上的,這就是為什麼Animation不會改變View的屬性的根本所在。另一方面,我們知道Animation僅在View被繪製的時候才能發揮自己的價值,這也是為什麼插間動畫被放在Android.view包內。

3.Animator執行原理和原始碼分析

3.1 屬性動畫的基本屬性

  • 屬性動畫跟補間動畫一樣會包含動畫相關的屬性,如動畫時長、動畫播放次數、延遲時間、插間器等等,為了後面分析動畫執行流程時概念更加明確,這裡僅僅寫了部分ValueAnimator原始碼中的欄位,並做了相應的註解
    // 初始化函式是否被呼叫 
    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; 
    // 插間器
    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; 
    
    // 回撥監聽器 
    // 確保AnimatorListener.onAnimationStart(Animator)僅被呼叫一次 
    private boolean mStartListenersCalled = false; 
    // start,end,cancel,repeat回撥
    ArrayList<AnimatorListener> mListeners = null; 
    // pause, resume回撥
    ArrayList<AnimatorPauseListener> mPauseListeners = null;  
    // value更新回撥
    ArrayList<AnimatorUpdateListener> mUpdateListeners = null; 
    

3.2 屬性動畫新的概念

  • 屬性動畫相對於插間動畫來件引入了一些新的概念
    • 可以暫停和恢復、可以調整進度,這些概念的引入,讓動畫的概念更加飽滿起來,讓動畫有了視訊播放的概念,主要有:
    // 動畫是否正在running
    private boolean mRunning = false;
    // 動畫是否被開始
    private boolean mStarted = false;
    // 動畫是否被暫停 
    boolean mPaused = false; 
    // 動畫暫停時間點,用於在動畫被恢復的時候調整mStartTime以確保動畫能優雅地繼續執行 
    private long mPauseTime; 
    // 動畫是否從暫停中被恢復,用於表明動畫可以調整mStartTime
    private boolean mResumed = false; 
    // 動畫被設定的進度位置
    float mSeekFraction = -1;
    

3.3 PropertyValuesHolder作用

  • PropertyValuesHolder是用來儲存某個屬性property對應的一組值,這些值對應了一個動畫週期中的所有關鍵幀。
    • 動畫說到底是由動畫幀組成的,將動畫幀連續起來就成了動畫呢。
    • Animator可以設定並儲存整個動畫週期中的關鍵幀,然後根據這些關鍵幀計算出動畫週期中任一時間點對應的動畫幀的動畫資料
    • 而每一幀的動畫資料裡都包含了一個時間點屬性fraction以及一個動畫值mValue,從而實現根據當前的時間點計算當前的動畫值,然後用這個動畫值去更新property對應的屬性
    • Animator被稱為屬性動畫的原因,因為它的整個動畫過程實際上就是不斷計算並更新物件的屬性這個後面詳細講解。
  • 那麼儲存property使用什麼儲存的呢?看程式碼可知:陣列
    • image
  • PropertyValuesHolder由Property及Keyframes組成,其中Property用於描述屬性的特徵:如屬性名以及屬性型別,並提供set及get方法用於獲取及設定給定Target的對應屬性值;Keyframes由一組關鍵幀Keyframe組成,每一個關鍵幀由fraction及value來定量描述,於是Keyframes可以根據給定的fraction定位到兩個關鍵幀,這兩個關鍵幀的fraction組成的區間包含給定的fraction,然後根據定位到的兩個關鍵幀以及設定插間器及求值器就可以計算出給定fraction對應的value。
    • PropertyValuesHolder的整個工作流程
      • 首先通過setObjectValues等函式來初始化關鍵幀組mKeyframes,必要的情況下(如ObjectAnimator)可以通過setStartValue及setEndValue來設定第一幀及最末幀的value,以上工作只是完成了PropertyValuesHolder的初始化,
      • 之後就可以由Animator在繪製動畫幀的時候通過fraction來呼叫calculateValue計算該fraction對應的value(實際上是由mKeyframes的getValue方法做出最終計算),獲得對應的value之後,一方面可以通過getAnimatedValue提供給Animator使用,
      • 另一方面也可以通過setAnimatedValue方法直接將該值設定到相應Target中去,這樣PropertyValuesHolder的職責也就完成呢。

3.4 屬性動畫start執行流程

  • 首先看看start方法,預設是false,這個引數是幹嘛的呢?這個引數是動畫是否應該開始反向播放。
    • 啟動動畫播放。這個版本的start()使用一個布林標誌,指示動畫是否應該反向播放。該標誌通常為false,但如果從反向()方法呼叫,則可以將其設定為true。通過呼叫此方法啟動的動畫將在呼叫此方法的執行緒上執行。這個執行緒應該有一個活套(如果不是這樣的話,將丟擲一個執行時異常)。另外,如果動畫將動畫化檢視層次結構中物件的屬性,那麼呼叫執行緒應該是該檢視層次結構的UI執行緒。
    @Override
    public void start() {
        start(false);
    }
    
    private void start(boolean playBackwards) {
        if (Looper.myLooper() == null) {
            throw new AndroidRuntimeException("Animators may only be run on Looper threads");
        }
        mReversing = playBackwards;
        mSelfPulse = !mSuppressSelfPulseRequested;
        if (playBackwards && mSeekFraction != -1 && mSeekFraction != 0) {
            if (mRepeatCount == INFINITE) {
                float fraction = (float) (mSeekFraction - Math.floor(mSeekFraction));
                mSeekFraction = 1 - fraction;
            } else {
                mSeekFraction = 1 + mRepeatCount - mSeekFraction;
            }
        }
        mStarted = true;
        mPaused = false;
        mRunning = false;
        mAnimationEndRequested = false;
        mLastFrameTime = -1;
        mFirstFrameTime = -1;
        mStartTime = -1;
        addAnimationCallback(0);
    
        if (mStartDelay == 0 || mSeekFraction >= 0 || mReversing) {
            startAnimation();
            if (mSeekFraction == -1) {
                setCurrentPlayTime(0);
            } else {
                setCurrentFraction(mSeekFraction);
            }
        }
    }
    
  • 然後接著看addAnimationCallback(0)這行程式碼,從字面意思理解是新增動畫回撥callback
    • 可以看到通過getAnimationHandler()建立了一個AnimationHandler物件。
    • 然後在看看addAnimationFrameCallback()這個方法,看命名應該是專門處理動畫相關的。實際上裡面的邏輯大概是:通過Choreographer向底層註冊下一個螢幕重新整理訊號監聽,然後將需要執行的動畫新增到列表中,如果延遲時間大於0,則說明動畫是一個延遲開始的動畫,那麼加入Delay佇列裡。
    • image
    • image
    • 然後看看動畫是用什麼儲存的呢?mAnimationCallbacks是一個ArrayList,每一項儲存的是 AnimationFrameCallback 介面的物件,看命名這是一個回撥介面
  • AnimationHandler的作用主要是什麼呢?
    • 是一個定時任務處理器,根據Choreographer的脈衝週期性地完成指定的任務,由於它是一個執行緒安全的靜態變數,因此執行在同一執行緒中的所有Animator共用一個定時任務處理器,這樣的好處在於:一方面可以保證Animator中計算某一時刻動畫幀是在同一執行緒中執行的,避免了多執行緒同步的問題;另一方面,該執行緒下所有動畫共用一個處理器,可以讓這些動畫有效地進行同步,從而讓動畫效果更加優雅。
    • image
  • 然後在回到start(boolean playBackwards)方法中,檢視startAnimation()原始碼。
    • 內部呼叫,通過將動畫新增到活動動畫列表來啟動動畫。必須在UI執行緒上呼叫。
    • 通過notifyStartListeners()這個方法,重新整理動畫listener,也就是通知動畫開始呢。
    • image
  • 接著看initAnimation()初始化動畫操作邏輯
    • 在處理動畫的第一個動畫幀之前立即呼叫此函式。如果存在非零startDelay,則在延遲結束後呼叫該函式,它負責動畫的最終初始化步驟。
    • image

3.5 屬性動畫cancel和end執行流程

  • 先看看cancel中的原始碼
    • 可以得知,cancel只會處理那些正在執行或者等待開始執行的動畫,大概的處理邏輯是這樣的:
      • 呼叫AnimatorListener.onAnimationCancel
      • 然後呼叫Animator.endAnimation
        • 通過removeAnimationCallback()把該動畫從AnimationHandler的所有列表中清除
        • 呼叫AnimatorListener.onAnimationEnd
        • 復位動畫所有狀態:如mPlayingState = STOPPED、mRunning=false、mReversing = false、mStarted = false等等
    • image
    • image
  • 再看看end中的原始碼
    • end相對於cancel來說有兩個區別:一個是會處理所有動畫;另一個是會計算最末一幀動畫值。其具體的處理邏輯如下所示:
      • 若動畫尚未開始:呼叫Animatior.startAnimation讓動畫處於正常執行狀態
      • 計算最後一幀動畫的動畫值:animateValue(mPlayingBackwards ? 0f : 1f)
      • 結束動畫就呼叫endAnimation這個方法,上面已經分析了該方法的作用
    • image

3.6 屬性動畫pase和resume執行流程

  • 先看看pause方法中的原始碼
    • 先看在Animator中的pause方法,然後看ValueAnimator中的pause方法可知:
    • 僅僅在動畫已開始(isStarted()==true)且當前為非暫停狀態時才進行以下處理
      • 置位:mPaused = true
      • 迴圈遍歷呼叫AnimatorPauseListener.onAnimationPause
      • 清空暫停時間:mPauseTime = -1
      • 復位mResumed = false
    //在ValueAnimator中
    public void pause() {
        boolean previouslyPaused = mPaused;
        super.pause();
        if (!previouslyPaused && mPaused) {
            mPauseTime = -1;
            mResumed = false;
        }
    }
    
    //在Animator中
    public void pause() {
        if (isStarted() && !mPaused) {
            mPaused = true;
            if (mPauseListeners != null) {
                ArrayList<AnimatorPauseListener> tmpListeners =
                        (ArrayList<AnimatorPauseListener>) mPauseListeners.clone();
                int numListeners = tmpListeners.size();
                for (int i = 0; i < numListeners; ++i) {
                    tmpListeners.get(i).onAnimationPause(this);
                }
            }
        }
    }
    
    • 做完這些處理之後,等下一幀動畫的到來,當doAnimationFrame被呼叫,此時若仍然處於暫停狀態,就會做如下截擊
      • 這樣就阻止了動畫的正常執行,並記錄下來動畫暫停的時間,確保恢復之後能讓動畫調整到暫停之前的動畫點正常執行,具體怎麼起作用就要看resume的作用。
    • image
  • 先看看resume方法中的原始碼
    • 先看在ValueAnimator中的resume方法,然後看Animator中的resume方法可知:
      • 置位:mResumed = true
      • 復位:mPaused = false
      • 呼叫AnimatorPauseListener.onAnimationResume
    //在ValueAnimator中
    @Override
    public void resume() {
        if (Looper.myLooper() == null) {
            throw new AndroidRuntimeException("Animators may only be resumed from the same " +
                    "thread that the animator was started on");
        }
        if (mPaused && !mResumed) {
            mResumed = true;
            if (mPauseTime > 0) {
                addAnimationCallback(0);
            }
        }
        super.resume();
    }
    
    //在Animator中
    public void resume() {
        if (mPaused) {
            mPaused = false;
            if (mPauseListeners != null) {
                ArrayList<AnimatorPauseListener> tmpListeners =
                        (ArrayList<AnimatorPauseListener>) mPauseListeners.clone();
                int numListeners = tmpListeners.size();
                for (int i = 0; i < numListeners; ++i) {
                    tmpListeners.get(i).onAnimationResume(this);
                }
            }
        }
    }
    
    • 當doAnimationFrame被呼叫,此時若處於恢復狀態(mResume==true),就會做如下補償處理
      • 這樣就讓暫停的時間從動畫的執行過程中消除
    • image

3.7 屬性動畫與View結合

  • 屬性動畫如何去實現View的變換?
    • 是根據計算出來的動畫值去修改View的屬性,如alpha、x、y、scaleX、scaleY、translationX、translationY等等,這樣當View重繪時就會產生作用,隨著View連續不斷地被重繪,就會產生絢爛多彩的動畫。
  • 接著看setTarget這個方法原始碼
    • 如果是使用ValueAnimator類,那麼直接通過mAnimator.setTarget(view)設定view
    • 如果是使用ObjectAnimator,那麼直接通過ObjectAnimator.ofFloat(view, type, start, end)設定view,最終還是會呼叫setTarget方法。注意ObjectAnimator實現了ValueAnimator類
    • ObjectAnimator是可以在動畫幀計算完成之後直接對Target屬性進行修改的屬性動畫型別,相對於ValueAnimator來說更加省心省力
  • 相比ValueAnimator類,ObjectAnimator還做了許多操作,ObjectAnimator與 ValueAnimator類的區別:
    • ValueAnimator 類是先改變值,然後 手動賦值 給物件的屬性從而實現動畫;是 間接 對物件屬性進行操作;
    • ObjectAnimator 類是先改變值,然後 自動賦值 給物件的屬性從而實現動畫;是 直接 對物件屬性進行操作;
  • 個人感覺屬性動畫原始碼分析十分具有跳躍性。不過還好沒有關係,只需要理解其大概運作原理就可以呢。

關於其他內容介紹

01.關於部落格彙總連結

02.關於我的部落格