目錄介紹
- 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萬字],轉載請註明出處,謝謝!
- 鏈接地址:https://github.com/yangchong211/YCBlogs
- 如果覺得好,可以star一下,謝謝!當然也歡迎提出建議,萬事起於忽微,量變引起質變!
- 01.動畫機制總結
- 02.動畫源碼解析
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方法,主要是分析這個方法邏輯,如圖所示
- 那麼這個方法中做了什麼呢?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
- 已執行次數mRepeatCount等於需執行次數mRepeated
2.3 什麼是動畫更新函數
- 下面我們來看一下getTransformation方法中的這一行代碼applyTransformation(interpolatedTime, outTransformation),然後進去看看這個方法。如下所示
- 這個方法的用途是幹啥呢?從這個英文解釋中可以得知:getTransform的助手。子類應該實現這一點,以應用給定的內插值來應用它們的轉換。該方法的實現應該總是替換指定的轉換或文檔,而不是這樣做的。
- 都知道Animation是個抽象類,接着我們這些逗比程序員可以看看它的某一個子類,比如看看ScaleAnimation中的applyTransformation方法吧。
- 是否設定縮放中心點:
- 若mPivotX==0 且 mPivotY==0:transformation.getMatrix().setScale(sx, sy)
- 否則:transformation.getMatrix().setScale(sx, sy, mPivotX, mPivotY)
- 是否設定縮放中心點:
- 介紹到這裏還是沒有講明白它的具體作用,它是在什麼情況下調用的。不要着急,接下來會慢慢分析的……
2.4 動畫數據如何存儲
- 可以看到applyTransformation(float interpolatedTime, Transformation t)這個方法中帶有一個Transformation參數,那麼這個參數是幹啥呢?
- 實際上,Animation的動畫函數getTransformation目的在於生成當前幀的一個Transformation,這個Transformation採用alpha以及Matrix存儲了一幀動畫的數據,Transformation包含兩種模式:
- alpha模式:用於支持透明度動畫
- matrix模式:用於支持縮放、平移以及旋轉動畫
- 同時,Transformation還提供了許多兩個接口用於組合多個Transformation:
- compose:前結合(alpha相乘、矩陣右乘、邊界疊加)
- postCompose:後結合(alpha相乘、矩陣左乘、邊界疊加
- 實際上,Animation的動畫函數getTransformation目的在於生成當前幀的一個Transformation,這個Transformation採用alpha以及Matrix存儲了一幀動畫的數據,Transformation包含兩種模式:
2.5 Animation的調用
- getTransformation這個函數究竟是在哪裏調用的?計算得到的動畫數據又是怎麼被應用的?爲什麼Animation這個包要放在android.view下面以及Animation完成之後爲什麼View本身的屬性不會被改變。慢慢看……
- 要了解Animation,先從要從Animation的基本使用View.startAnimation開始尋根溯源:如下所示
- 接着看看setStartTime這個方法,主要是設置一些屬性。
- 接着看看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)
- 接着看看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)設定繪製區域
- 初始化Animation(僅初始化一次)
- View.applyLegacyAnimation就是Animation大顯神通的舞臺,其核心代碼主要分三個部分
- 當View.applyLegacyAnimation調用完成之後,View此次繪製的動畫數據就構建完成,之後便回到View.draw(Canvas, ViewGroup, long)應用動畫數據對視圖進行繪製刷新,如下所示:
- 重點看到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使用什麼存儲的呢?看代碼可知:數組
- 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的職責也就完成呢。
- 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隊列裏。
- 然後看看動畫是用什麼存儲的呢?mAnimationCallbacks是一個ArrayList,每一項保存的是 AnimationFrameCallback 接口的對象,看命名這是一個回調接口
- AnimationHandler的作用主要是什麼呢?
- 是一個定時任務處理器,根據Choreographer的脈衝週期性地完成指定的任務,由於它是一個線程安全的靜態變量,因此運行在同一線程中的所有Animator共用一個定時任務處理器,這樣的好處在於:一方面可以保證Animator中計算某一時刻動畫幀是在同一線程中運行的,避免了多線程同步的問題;另一方面,該線程下所有動畫共用一個處理器,可以讓這些動畫有效地進行同步,從而讓動畫效果更加優雅。
- 然後在回到start(boolean playBackwards)方法中,查看startAnimation()源碼。
- 內部調用,通過將動畫添加到活動動畫列表來啓動動畫。必須在UI線程上調用。
- 通過notifyStartListeners()這個方法,刷新動畫listener,也就是通知動畫開始呢。
- 接着看initAnimation()初始化動畫操作邏輯
- 在處理動畫的第一個動畫幀之前立即調用此函數。如果存在非零<code>startDelay</code>,則在延遲結束後調用該函數,它負責動畫的最終初始化步驟。
3.5 屬性動畫cancel和end執行流程
- 先看看cancel中的源碼
- 可以得知,cancel只會處理那些正在運行或者等待開始運行的動畫,大概的處理邏輯是這樣的:
- 調用AnimatorListener.onAnimationCancel
- 然後調用Animator.endAnimation
- 通過removeAnimationCallback()把該動畫從AnimationHandler的所有列表中清除
- 調用AnimatorListener.onAnimationEnd
- 復位動畫所有狀態:如mPlayingState = STOPPED、mRunning=false、mReversing = false、mStarted = false等等
- 可以得知,cancel只會處理那些正在運行或者等待開始運行的動畫,大概的處理邏輯是這樣的:
- 再看看end中的源碼
- end相對於cancel來說有兩個區別:一個是會處理所有動畫;另一個是會計算最末一幀動畫值。其具體的處理邏輯如下所示:
- 若動畫尚未開始:調用Animatior.startAnimation讓動畫處於正常運行狀態
- 計算最後一幀動畫的動畫值:animateValue(mPlayingBackwards ? 0f : 1f)
- 結束動畫就調用endAnimation這個方法,上面已經分析了該方法的作用
- end相對於cancel來說有兩個區別:一個是會處理所有動畫;另一個是會計算最末一幀動畫值。其具體的處理邏輯如下所示:
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的作用。
- 先看看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),就會做如下補償處理
- 這樣就讓暫停的時間從動畫的運行過程中消除
- 先看在ValueAnimator中的resume方法,然後看Animator中的resume方法可知:
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.關於我的博客
- 我的個人站點:www.yczbj.org,www.ycbjie.cn
- github:https://github.com/yangchong211
- 知乎:https://www.zhihu.com/people/yang-chong-69-24/pins/posts
- 簡書:http://www.jianshu.com/u/b7b2c6ed9284
- csdn:http://my.csdn.net/m0_37700275
- 喜馬拉雅聽書:http://www.ximalaya.com/zhubo/71989305/
- 開源中國:https://my.oschina.net/zbj1618/blog
- 泡在網上的日子:http://www.jcodecraeer.com/member/content_list.php?channelid=1
- 郵箱:[email protected]
- 阿里雲博客:https://yq.aliyun.com/users/article?spm=5176.100- 239.headeruserinfo.3.dT4bcV
- segmentfault頭條:https://segmentfault.com/u/xiangjianyu/articles