摘要:目錄介紹和區(qū)別運(yùn)行原理和源碼分析基本屬性介紹如何計(jì)算動(dòng)畫數(shù)據(jù)什么是動(dòng)畫更新函數(shù)動(dòng)畫數(shù)據(jù)如何存儲(chǔ)的調(diào)用運(yùn)行原理和源碼分析屬性動(dòng)畫的基本屬性屬性動(dòng)畫新的概念作用屬性動(dòng)畫執(zhí)行流程屬性動(dòng)畫和執(zhí)行流程屬性動(dòng)畫和執(zhí)行流程屬性動(dòng)畫與結(jié)合好消息博客筆記大匯
目錄介紹
1.Animation和Animator區(qū)別
2.Animation運(yùn)行原理和源碼分析
2.1 基本屬性介紹
2.2 如何計(jì)算動(dòng)畫數(shù)據(jù)
2.3 什么是動(dòng)畫更新函數(shù)
2.4 動(dòng)畫數(shù)據(jù)如何存儲(chǔ)
2.5 Animation的調(diào)用
3.Animator運(yùn)行原理和源碼分析
3.1 屬性動(dòng)畫的基本屬性
3.2 屬性動(dòng)畫新的概念
3.3 PropertyValuesHolder作用
3.4 屬性動(dòng)畫start執(zhí)行流程
3.5 屬性動(dòng)畫cancel和end執(zhí)行流程
3.6 屬性動(dòng)畫pase和resume執(zhí)行流程
3.7 屬性動(dòng)畫與View結(jié)合
好消息博客筆記大匯總【16年3月到至今】,包括Java基礎(chǔ)及深入知識(shí)點(diǎn),Android技術(shù)博客,Python學(xué)習(xí)筆記等等,還包括平時(shí)開發(fā)中遇到的bug匯總,當(dāng)然也在工作之余收集了大量的面試題,長期更新維護(hù)并且修正,持續(xù)完善……開源的文件是markdown格式的!同時(shí)也開源了生活博客,從12年起,積累共計(jì)47篇[近20萬字],轉(zhuǎn)載請注明出處,謝謝!
鏈接地址:https://github.com/yangchong2...
如果覺得好,可以star一下,謝謝!當(dāng)然也歡迎提出建議,萬事起于忽微,量變引起質(zhì)變!
[01.動(dòng)畫機(jī)制總結(jié)]()
02.動(dòng)畫源碼解析
1.Animation和Animator區(qū)別
對于 Animation 動(dòng)畫:
實(shí)現(xiàn)機(jī)制是,在每次進(jìn)行繪圖的時(shí)候,通過對整塊畫布的矩陣進(jìn)行變換,從而實(shí)現(xiàn)一種視圖坐標(biāo)的移動(dòng),但實(shí)際上其在 View內(nèi)部真實(shí)的坐標(biāo)位置及其他相關(guān)屬性始終恒定.
對于 Animator 動(dòng)畫:
Animator動(dòng)畫的實(shí)現(xiàn)機(jī)制說起來其實(shí)更加簡單一點(diǎn),因?yàn)樗鋵?shí)只是計(jì)算動(dòng)畫開啟之后,結(jié)束之前,到某個(gè)時(shí)間點(diǎn)得時(shí)候,某個(gè)屬性應(yīng)該有的值,然后通過回調(diào)接口去設(shè)置具體值,其實(shí) Animator 內(nèi)部并沒有針對某個(gè) view 進(jìn)行刷新,來實(shí)現(xiàn)動(dòng)畫的行為,動(dòng)畫的實(shí)現(xiàn)是在設(shè)置具體值的時(shí)候,方法內(nèi)部自行調(diào)取的類似 invalidate 之類的方法實(shí)現(xiàn)的.也就是說,使用 Animator ,內(nèi)部的屬性發(fā)生了變化
或者更簡單一點(diǎn)說
前者屬性動(dòng)畫,改變控件屬性,(比如平移以后點(diǎn)擊有事件觸發(fā))
后者補(bǔ)間動(dòng)畫,只產(chǎn)生動(dòng)畫效果(平移之后點(diǎn)無事件觸發(fā),前提是你fillafter=true)
2.Animation運(yùn)行原理和源碼分析 2.1 基本屬性介紹
上一篇文章已經(jīng)對補(bǔ)間動(dòng)畫做了詳細(xì)的說明,不過這里還是需要重復(fù)說一下動(dòng)畫屬性的作用
mStartTime:動(dòng)畫實(shí)際開始時(shí)間
mStartOffset:動(dòng)畫延遲時(shí)間
mFillEnabled:mFillBefore及mFillAfter是否使能
mFillBefore:動(dòng)畫結(jié)束之后是否需要進(jìn)行應(yīng)用動(dòng)畫
mFillAfter:動(dòng)畫開始之前是否需要進(jìn)行應(yīng)用動(dòng)畫
mDuration:單次動(dòng)畫運(yùn)行時(shí)長
mRepeatMode:動(dòng)畫重復(fù)模式(RESTART、REVERSE)
mRepeatCount:動(dòng)畫重復(fù)次數(shù)(INFINITE,直接值)
mInterceptor:動(dòng)畫插間器
mBackgroundColor:動(dòng)畫背景顏色
mListener:動(dòng)畫開始、結(jié)束、重復(fù)回調(diào)監(jiān)聽器
2.2 如何計(jì)算動(dòng)畫數(shù)據(jù)
首先進(jìn)入Animation類,然后找到getTransformation方法,主要是分析這個(gè)方法邏輯,如圖所示
那么這個(gè)方法中做了什么呢?Animation在其getTransformation函數(shù)被調(diào)用時(shí)會(huì)計(jì)算一幀動(dòng)畫數(shù)據(jù),而上面這些屬性基本都是在計(jì)算動(dòng)畫數(shù)據(jù)時(shí)有相關(guān)的作用。
第一步:若startTime為START_ON_FIRST_FRAME(值為-1)時(shí),將startTime設(shè)定為curTime
第二步:計(jì)算當(dāng)前動(dòng)畫進(jìn)度:
normalizedTime = (curTime - (startTime + startOffset))/duration
若mFillEnabled==false:將normalisedTime夾逼至[0.0f, 1.0f]
第三步:判斷是否需要計(jì)算動(dòng)畫數(shù)據(jù):
若normalisedTime在[0.0f, 1.0f],需計(jì)算動(dòng)畫數(shù)據(jù)
若normalisedTime不在[0.0f, 1.0f]:
normalisedTime<0.0f, 僅當(dāng)mFillBefore==true時(shí)才計(jì)算動(dòng)畫數(shù)據(jù)
normalisedTime>1.0f, 僅當(dāng)mFillAfter==true時(shí)才計(jì)算動(dòng)畫數(shù)據(jù)
第四步:若需需要計(jì)算動(dòng)畫數(shù)據(jù):
若當(dāng)前為第一幀動(dòng)畫,觸發(fā)mListener.onAnimationStart
若mFillEnabled==false:將normalisedTime夾逼至[0.0f, 1.0f]
根據(jù)插間器mInterpolator調(diào)整動(dòng)畫進(jìn)度:
interpolatedTime = mInterpolator.getInterpolation(normalizedTime)
若動(dòng)畫反轉(zhuǎn)標(biāo)志位mCycleFlip為true,則
interpolatedTime = 1.0 - normalizedTime
調(diào)用動(dòng)畫更新函數(shù)applyTransformation(interpolatedTime, transformation)計(jì)算出動(dòng)畫數(shù)據(jù)
第五步:若夾逼之前normalisedTime大于1.0f, 則判斷是否需繼續(xù)執(zhí)行動(dòng)畫:
已執(zhí)行次數(shù)mRepeatCount等于需執(zhí)行次數(shù)mRepeated
若未觸發(fā)mListener.onAnimationEnd,則觸發(fā)之
已執(zhí)行次數(shù)mRepeatCount不等于需執(zhí)行次數(shù)mRepeated
自增mRepeatCount
重置mStartTime為-1
若mRepeatMode為REVERSE,則取反mCycleFlip
觸發(fā)mListener.onAnimationRepeat
2.3 什么是動(dòng)畫更新函數(shù)
下面我們來看一下getTransformation方法中的這一行代碼applyTransformation(interpolatedTime, outTransformation),然后進(jìn)去看看這個(gè)方法。如下所示
這個(gè)方法的用途是干啥呢?從這個(gè)英文解釋中可以得知:getTransform的助手。子類應(yīng)該實(shí)現(xiàn)這一點(diǎn),以應(yīng)用給定的內(nèi)插值來應(yīng)用它們的轉(zhuǎn)換。該方法的實(shí)現(xiàn)應(yīng)該總是替換指定的轉(zhuǎn)換或文檔,而不是這樣做的。
都知道Animation是個(gè)抽象類,接著我們這些逗比程序員可以看看它的某一個(gè)子類,比如看看ScaleAnimation中的applyTransformation方法吧。
是否設(shè)定縮放中心點(diǎn):
若mPivotX==0 且 mPivotY==0:transformation.getMatrix().setScale(sx, sy)
否則:transformation.getMatrix().setScale(sx, sy, mPivotX, mPivotY)
介紹到這里還是沒有講明白它的具體作用,它是在什么情況下調(diào)用的。不要著急,接下來會(huì)慢慢分析的……
2.4 動(dòng)畫數(shù)據(jù)如何存儲(chǔ)
可以看到applyTransformation(float interpolatedTime, Transformation t)這個(gè)方法中帶有一個(gè)Transformation參數(shù),那么這個(gè)參數(shù)是干啥呢?
實(shí)際上,Animation的動(dòng)畫函數(shù)getTransformation目的在于生成當(dāng)前幀的一個(gè)Transformation,這個(gè)Transformation采用alpha以及Matrix存儲(chǔ)了一幀動(dòng)畫的數(shù)據(jù),Transformation包含兩種模式:
alpha模式:用于支持透明度動(dòng)畫
matrix模式:用于支持縮放、平移以及旋轉(zhuǎn)動(dòng)畫
同時(shí),Transformation還提供了許多兩個(gè)接口用于組合多個(gè)Transformation:
compose:前結(jié)合(alpha相乘、矩陣右乘、邊界疊加)
postCompose:后結(jié)合(alpha相乘、矩陣左乘、邊界疊加
2.5 Animation的調(diào)用
getTransformation這個(gè)函數(shù)究竟是在哪里調(diào)用的?計(jì)算得到的動(dòng)畫數(shù)據(jù)又是怎么被應(yīng)用的?為什么Animation這個(gè)包要放在android.view下面以及Animation完成之后為什么View本身的屬性不會(huì)被改變。慢慢看……
要了解Animation,先從要從Animation的基本使用View.startAnimation開始尋根溯源:如下所示
接著看看setStartTime這個(gè)方法,主要是設(shè)置一些屬性。
接著看看setAnimation(animation)方法源碼
設(shè)置要為此視圖播放的下一個(gè)動(dòng)畫。如果希望動(dòng)畫立即播放,請使用{@link#startAnimation(android.view.animation.Animation)}代替此方法,該方法允許對啟動(dòng)時(shí)間和無效時(shí)間進(jìn)行細(xì)粒度控制,但必須確保動(dòng)畫具有啟動(dòng)時(shí)間集,并且當(dāng)動(dòng)畫應(yīng)該啟動(dòng)時(shí),視圖的父視圖(控制子視圖上的動(dòng)畫)將失效。
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(); } }
接著重點(diǎn)看一下invalidate(true)這個(gè)方法
通過invalidate(true)函數(shù)會(huì)觸發(fā)View的重新繪制,那么在View.draw是怎么走到對Animation的處理函數(shù)呢?
View.draw(Canvas) —> ViewGroup.dispatchDraw(Canvas) —> ViewGroup.drawChild(Canvas, View, long) —> View.draw(Canvas, ViewGroup, long) —> View.applyLegacyAnimation(ViewGroup, long, Animation, boolean)
接著看看View中applyLegacyAnimation這個(gè)方法
private boolean applyLegacyAnimation(ViewGroup parent, long drawingTime, Animation a, boolean scalingRequired) { Transformation invalidationTransform; final int flags = parent.mGroupFlags; //判斷Animation是否初始化 final boolean initialized = a.isInitialized(); //如果沒有初始化,則進(jìn)行初始化 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); //由父視圖組調(diào)用,通知當(dāng)前與此視圖關(guān)聯(lián)的動(dòng)畫的開始。如果重寫此方法,則始終調(diào)用Super.on動(dòng)畫Start(); onAnimationStart(); } //獲取Transformation對象 final Transformation t = parent.getChildTransformation(); //獲取要在指定時(shí)間點(diǎn)應(yīng)用的轉(zhuǎn)換,這個(gè)方法最終調(diào)用了Animation中的getTransformation方法 //調(diào)用getTransformation根據(jù)當(dāng)前繪制事件生成Animation中對應(yīng)幀的動(dòng)畫數(shù)據(jù) 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; } //下面主要是,根據(jù)動(dòng)畫數(shù)據(jù)設(shè)定重繪制區(qū)域 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; //調(diào)用ViewGroup.invalidate(int l, int t, int r, int b)設(shè)定繪制區(qū)域 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; //調(diào)用ViewGroup.invalidate(int l, int t, int r, int b)設(shè)定繪制區(qū)域 parent.invalidate(left, top, left + (int) (region.width() + .5f), top + (int) (region.height() + .5f)); } } return more; }
View.applyLegacyAnimation就是Animation大顯神通的舞臺(tái),其核心代碼主要分三個(gè)部分
初始化Animation(僅初始化一次)
調(diào)用Animation.initialize(width, height, parentWidth, parentHeight),通過View及ParentView的Size來解析Animation中的相關(guān)數(shù)據(jù);
調(diào)用Animation.initializeInvalidateRegion(left, top, right, bottom)來設(shè)定動(dòng)畫的初始區(qū)域,并在fillBefore為true時(shí)計(jì)算Animation動(dòng)畫進(jìn)度為0.0f的數(shù)據(jù)
調(diào)用getTransformation根據(jù)當(dāng)前繪制事件生成Animation中對應(yīng)幀的動(dòng)畫數(shù)據(jù)
根據(jù)動(dòng)畫數(shù)據(jù)設(shè)定重繪制區(qū)域
若僅為Alpha動(dòng)畫,此時(shí)動(dòng)畫區(qū)域?yàn)閂iew的當(dāng)前區(qū)域,且不會(huì)產(chǎn)生變化
若包含非Alpha動(dòng)畫,此時(shí)動(dòng)畫區(qū)域需要調(diào)用Animation.getInvalidateRegion進(jìn)行計(jì)算,該函數(shù)會(huì)根據(jù)上述生成動(dòng)畫數(shù)據(jù)Thransformation中的Matrix進(jìn)行計(jì)算,并與之前的動(dòng)畫區(qū)域執(zhí)行unio操作,從而獲取動(dòng)畫的完整區(qū)域
調(diào)用ViewGroup.invalidate(int l, int t, int r, int b)設(shè)定繪制區(qū)域
當(dāng)View.applyLegacyAnimation調(diào)用完成之后,View此次繪制的動(dòng)畫數(shù)據(jù)就構(gòu)建完成,之后便回到View.draw(Canvas, ViewGroup, long)應(yīng)用動(dòng)畫數(shù)據(jù)對視圖進(jìn)行繪制刷新,如下所示:
重點(diǎn)看到Animation產(chǎn)生的動(dòng)畫數(shù)據(jù)實(shí)際并不是應(yīng)用在View本身的,而是應(yīng)用在RenderNode或者Canvas上的,這就是為什么Animation不會(huì)改變View的屬性的根本所在。另一方面,我們知道Animation僅在View被繪制的時(shí)候才能發(fā)揮自己的價(jià)值,這也是為什么插間動(dòng)畫被放在Android.view包內(nèi)。
3.Animator運(yùn)行原理和源碼分析 3.1 屬性動(dòng)畫的基本屬性
屬性動(dòng)畫跟補(bǔ)間動(dòng)畫一樣會(huì)包含動(dòng)畫相關(guān)的屬性,如動(dòng)畫時(shí)長、動(dòng)畫播放次數(shù)、延遲時(shí)間、插間器等等,為了后面分析動(dòng)畫運(yùn)行流程時(shí)概念更加明確,這里僅僅寫了部分ValueAnimator源碼中的字段,并做了相應(yīng)的注解
// 初始化函數(shù)是否被調(diào)用 boolean mInitialized = false; // 動(dòng)畫時(shí)長 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; // 插間器 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)聽器 // 確保AnimatorListener.onAnimationStart(Animator)僅被調(diào)用一次 private boolean mStartListenersCalled = false; // start,end,cancel,repeat回調(diào) ArrayList3.2 屬性動(dòng)畫新的概念mListeners = null; // pause, resume回調(diào) ArrayList mPauseListeners = null; // value更新回調(diào) ArrayList mUpdateListeners = null;
屬性動(dòng)畫相對于插間動(dòng)畫來件引入了一些新的概念
可以暫停和恢復(fù)、可以調(diào)整進(jìn)度,這些概念的引入,讓動(dòng)畫的概念更加飽滿起來,讓動(dòng)畫有了視頻播放的概念,主要有:
// 動(dòng)畫是否正在running private boolean mRunning = false; // 動(dòng)畫是否被開始 private boolean mStarted = false; // 動(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)度位置 float mSeekFraction = -1;3.3 PropertyValuesHolder作用
PropertyValuesHolder是用來保存某個(gè)屬性property對應(yīng)的一組值,這些值對應(yīng)了一個(gè)動(dòng)畫周期中的所有關(guān)鍵幀。
動(dòng)畫說到底是由動(dòng)畫幀組成的,將動(dòng)畫幀連續(xù)起來就成了動(dòng)畫呢。
Animator可以設(shè)定并保存整個(gè)動(dòng)畫周期中的關(guān)鍵幀,然后根據(jù)這些關(guān)鍵幀計(jì)算出動(dòng)畫周期中任一時(shí)間點(diǎn)對應(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對應(yīng)的屬性
Animator被稱為屬性動(dòng)畫的原因,因?yàn)樗恼麄€(gè)動(dòng)畫過程實(shí)際上就是不斷計(jì)算并更新對象的屬性這個(gè)后面詳細(xì)講解。
那么保存property使用什么存儲(chǔ)的呢?看代碼可知:數(shù)組
PropertyValuesHolder由Property及Keyframes組成,其中Property用于描述屬性的特征:如屬性名以及屬性類型,并提供set及get方法用于獲取及設(shè)定給定Target的對應(yīng)屬性值;Keyframes由一組關(guān)鍵幀Keyframe組成,每一個(gè)關(guān)鍵幀由fraction及value來定量描述,于是Keyframes可以根據(jù)給定的fraction定位到兩個(gè)關(guān)鍵幀,這兩個(gè)關(guān)鍵幀的fraction組成的區(qū)間包含給定的fraction,然后根據(jù)定位到的兩個(gè)關(guān)鍵幀以及設(shè)定插間器及求值器就可以計(jì)算出給定fraction對應(yīng)的value。
PropertyValuesHolder的整個(gè)工作流程
首先通過setObjectValues等函數(shù)來初始化關(guān)鍵幀組mKeyframes,必要的情況下(如ObjectAnimator)可以通過setStartValue及setEndValue來設(shè)定第一幀及最末幀的value,以上工作只是完成了PropertyValuesHolder的初始化,
之后就可以由Animator在繪制動(dòng)畫幀的時(shí)候通過fraction來調(diào)用calculateValue計(jì)算該fraction對應(yīng)的value(實(shí)際上是由mKeyframes的getValue方法做出最終計(jì)算),獲得對應(yīng)的value之后,一方面可以通過getAnimatedValue提供給Animator使用,
另一方面也可以通過setAnimatedValue方法直接將該值設(shè)定到相應(yīng)Target中去,這樣PropertyValuesHolder的職責(zé)也就完成呢。
3.4 屬性動(dòng)畫start執(zhí)行流程
首先看看start方法,默認(rèn)是false,這個(gè)參數(shù)是干嘛的呢?這個(gè)參數(shù)是動(dòng)畫是否應(yīng)該開始反向播放。
啟動(dòng)動(dòng)畫播放。這個(gè)版本的start()使用一個(gè)布爾標(biāo)志,指示動(dòng)畫是否應(yīng)該反向播放。該標(biāo)志通常為false,但如果從反向()方法調(diào)用,則可以將其設(shè)置為true。通過調(diào)用此方法啟動(dòng)的動(dòng)畫將在調(diào)用此方法的線程上運(yùn)行。這個(gè)線程應(yīng)該有一個(gè)活套(如果不是這樣的話,將拋出一個(gè)運(yùn)行時(shí)異常)。另外,如果動(dòng)畫將動(dòng)畫化視圖層次結(jié)構(gòu)中對象的屬性,那么調(diào)用線程應(yīng)該是該視圖層次結(jié)構(gòu)的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)這行代碼,從字面意思理解是添加動(dòng)畫回調(diào)callback
可以看到通過getAnimationHandler()創(chuàng)建了一個(gè)AnimationHandler對象。
然后在看看addAnimationFrameCallback()這個(gè)方法,看命名應(yīng)該是專門處理動(dòng)畫相關(guān)的。實(shí)際上里面的邏輯大概是:通過Choreographer向底層注冊下一個(gè)屏幕刷新信號(hào)監(jiān)聽,然后將需要運(yùn)行的動(dòng)畫添加到列表中,如果延遲時(shí)間大于0,則說明動(dòng)畫是一個(gè)延遲開始的動(dòng)畫,那么加入Delay隊(duì)列里。
然后看看動(dòng)畫是用什么存儲(chǔ)的呢?mAnimationCallbacks是一個(gè)ArrayList,每一項(xiàng)保存的是 AnimationFrameCallback 接口的對象,看命名這是一個(gè)回調(diào)接口
AnimationHandler的作用主要是什么呢?
是一個(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)雅。
然后在回到start(boolean playBackwards)方法中,查看startAnimation()源碼。
內(nèi)部調(diào)用,通過將動(dòng)畫添加到活動(dòng)動(dòng)畫列表來啟動(dòng)動(dòng)畫。必須在UI線程上調(diào)用。
通過notifyStartListeners()這個(gè)方法,刷新動(dòng)畫listener,也就是通知?jiǎng)赢嬮_始呢。
接著看initAnimation()初始化動(dòng)畫操作邏輯
在處理動(dòng)畫的第一個(gè)動(dòng)畫幀之前立即調(diào)用此函數(shù)。如果存在非零startDelay,則在延遲結(jié)束后調(diào)用該函數(shù),它負(fù)責(zé)動(dòng)畫的最終初始化步驟。
3.5 屬性動(dòng)畫cancel和end執(zhí)行流程
先看看cancel中的源碼
可以得知,cancel只會(huì)處理那些正在運(yùn)行或者等待開始運(yùn)行的動(dòng)畫,大概的處理邏輯是這樣的:
調(diào)用AnimatorListener.onAnimationCancel
然后調(diào)用Animator.endAnimation
通過removeAnimationCallback()把該動(dòng)畫從AnimationHandler的所有列表中清除
調(diào)用AnimatorListener.onAnimationEnd
復(fù)位動(dòng)畫所有狀態(tài):如mPlayingState = STOPPED、mRunning=false、mReversing = false、mStarted = false等等
再看看end中的源碼
end相對于cancel來說有兩個(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這個(gè)方法,上面已經(jīng)分析了該方法的作用
3.6 屬性動(dòng)畫pase和resume執(zhí)行流程
先看看pause方法中的源碼
先看在Animator中的pause方法,然后看ValueAnimator中的pause方法可知:
僅僅在動(dòng)畫已開始(isStarted()==true)且當(dāng)前為非暫停狀態(tài)時(shí)才進(jìn)行以下處理
置位:mPaused = true
循環(huán)遍歷調(diào)用AnimatorPauseListener.onAnimationPause
清空暫停時(shí)間:mPauseTime = -1
復(fù)位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) { ArrayListtmpListeners = (ArrayList ) mPauseListeners.clone(); int numListeners = tmpListeners.size(); for (int i = 0; i < numListeners; ++i) { tmpListeners.get(i).onAnimationPause(this); } } } }
- 做完這些處理之后,等下一幀動(dòng)畫的到來,當(dāng)doAnimationFrame被調(diào)用,此時(shí)若仍然處于暫停狀態(tài),就會(huì)做如下截?fù)? - 這樣就阻止了動(dòng)畫的正常運(yùn)行,并記錄下來動(dòng)畫暫停的時(shí)間,確?;謴?fù)之后能讓動(dòng)畫調(diào)整到暫停之前的動(dòng)畫點(diǎn)正常運(yùn)行,具體怎么起作用就要看resume的作用。 - ![image](https://upload-images.jianshu.io/upload_images/4432347-3bffba42cdadef07.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
先看看resume方法中的源碼
先看在ValueAnimator中的resume方法,然后看Animator中的resume方法可知:
置位:mResumed = true
復(fù)位:mPaused = false
調(diào)用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) { ArrayListtmpListeners = (ArrayList ) mPauseListeners.clone(); int numListeners = tmpListeners.size(); for (int i = 0; i < numListeners; ++i) { tmpListeners.get(i).onAnimationResume(this); } } } }
- 當(dāng)doAnimationFrame被調(diào)用,此時(shí)若處于恢復(fù)狀態(tài)(mResume==true),就會(huì)做如下補(bǔ)償處理 - 這樣就讓暫停的時(shí)間從動(dòng)畫的運(yùn)行過程中消除 - ![image](https://upload-images.jianshu.io/upload_images/4432347-1b43530b23fa712b.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)3.7 屬性動(dòng)畫與View結(jié)合
屬性動(dòng)畫如何去實(shí)現(xiàn)View的變換?
是根據(jù)計(jì)算出來的動(dòng)畫值去修改View的屬性,如alpha、x、y、scaleX、scaleY、translationX、translationY等等,這樣當(dāng)View重繪時(shí)就會(huì)產(chǎn)生作用,隨著View連續(xù)不斷地被重繪,就會(huì)產(chǎn)生絢爛多彩的動(dòng)畫。
接著看setTarget這個(gè)方法源碼
如果是使用ValueAnimator類,那么直接通過mAnimator.setTarget(view)設(shè)置view
如果是使用ObjectAnimator,那么直接通過ObjectAnimator.ofFloat(view, type, start, end)設(shè)置view,最終還是會(huì)調(diào)用setTarget方法。注意ObjectAnimator實(shí)現(xiàn)了ValueAnimator類
ObjectAnimator是可以在動(dòng)畫幀計(jì)算完成之后直接對Target屬性進(jìn)行修改的屬性動(dòng)畫類型,相對于ValueAnimator來說更加省心省力
相比ValueAnimator類,ObjectAnimator還做了許多操作,ObjectAnimator與 ValueAnimator類的區(qū)別:
ValueAnimator 類是先改變值,然后 手動(dòng)賦值 給對象的屬性從而實(shí)現(xiàn)動(dòng)畫;是 間接 對對象屬性進(jìn)行操作;
ObjectAnimator 類是先改變值,然后 自動(dòng)賦值 給對象的屬性從而實(shí)現(xiàn)動(dòng)畫;是 直接 對對象屬性進(jìn)行操作;
個(gè)人感覺屬性動(dòng)畫源碼分析十分具有跳躍性。不過還好沒有關(guān)系,只需要理解其大概運(yùn)作原理就可以呢。
關(guān)于其他內(nèi)容介紹 01.關(guān)于博客匯總鏈接1.技術(shù)博客匯總
2.開源項(xiàng)目匯總
3.生活博客匯總
4.喜馬拉雅音頻匯總
5.其他匯總
02.關(guān)于我的博客我的個(gè)人站點(diǎn):www.yczbj.org,www.ycbjie.cn
github:https://github.com/yangchong211
知乎:https://www.zhihu.com/people/...
簡書:http://www.jianshu.com/u/b7b2...
csdn:http://my.csdn.net/m0_37700275
喜馬拉雅聽書:http://www.ximalaya.com/zhubo...
開源中國:https://my.oschina.net/zbj161...
泡在網(wǎng)上的日子:http://www.jcodecraeer.com/me...
郵箱:yangchong211@163.com
阿里云博客:https://yq.aliyun.com/users/a... 239.headeruserinfo.3.dT4bcV
segmentfault頭條:https://segmentfault.com/u/xi...
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/72342.html
摘要:除官方外的參考文章微信小程序?qū)嵗齽?chuàng)建下發(fā)模板消息實(shí)例手把手教你開發(fā)微信小程序之模版消息開發(fā)教你突破小程序模板消息的推送限制獲取用戶信息接口的廢棄問題接口是獲取用戶信息昵稱,頭像等的接口,在官方文檔上寫是即將廢棄。 ----------------更新-------------- 2018年10月10日官網(wǎng)3個(gè)接口廢棄的通知: 1、分享監(jiān)聽接口分享消息給好友時(shí),開發(fā)者將無法從callba...
移動(dòng)端彈窗插件第二版,包括常見的 alert、confirm、toast、notice 四種類型彈窗,支持 jQuery 和 Zepto 庫。 特性 支持常見的 alert、confirm、toast、notice 四種類型彈窗 可選擇使用 IOS 或者 Material Design 風(fēng)格的彈窗 可自定義按鈕的文字、樣式、回調(diào)函數(shù),支持多個(gè)按鈕 多個(gè)彈窗狀態(tài)改變回調(diào)函數(shù) 同時(shí)支持 jQuery...
摘要:三更新內(nèi)容在原來項(xiàng)目的基礎(chǔ)上,做了如下更新數(shù)據(jù)庫重新設(shè)計(jì),改成以用戶分組的數(shù)據(jù)庫結(jié)構(gòu)應(yīng)數(shù)據(jù)庫改動(dòng),所有接口重新設(shè)計(jì),并統(tǒng)一采用和網(wǎng)易立馬理財(cái)一致的接口風(fēng)格刪除原來游客模式,增加登錄注冊功能,支持彈窗登錄。 這個(gè)項(xiàng)目最初其實(shí)是fork別人的項(xiàng)目。當(dāng)初想接觸下mongodb數(shù)據(jù)庫,找個(gè)例子學(xué)習(xí)下,后來改著改著就面目全非了。后臺(tái)和數(shù)據(jù)庫重構(gòu),前端增加了登錄注冊功能,僅保留了博客設(shè)置頁面,但是...
摘要:隨后會(huì)陸續(xù)發(fā)布及相關(guān)版本的插件。這和圖片查看器的操作方式是相同的。目前的調(diào)整大小存在一點(diǎn),但不影響整體的使用。鍵盤控制和照片查看器的按鍵是一樣的。除了照片查看器,的圖片查看器也非常的高大上。 showImg(https://segmentfault.com/img/remote/1460000012565638?w=750&h=375); 前言 因?yàn)橐恍┨厥獾臉I(yè)務(wù)需求,經(jīng)過一個(gè)多月的蟄...
摘要:隨后會(huì)陸續(xù)發(fā)布及相關(guān)版本的插件。這和圖片查看器的操作方式是相同的。目前的調(diào)整大小存在一點(diǎn),但不影響整體的使用。鍵盤控制和照片查看器的按鍵是一樣的。除了照片查看器,的圖片查看器也非常的高大上。 showImg(https://segmentfault.com/img/remote/1460000012565638?w=750&h=375); 前言 因?yàn)橐恍┨厥獾臉I(yè)務(wù)需求,經(jīng)過一個(gè)多月的蟄...
閱讀 1751·2023-04-25 22:42
閱讀 2202·2021-09-22 15:16
閱讀 3485·2021-08-30 09:44
閱讀 485·2019-08-29 16:44
閱讀 3303·2019-08-29 16:20
閱讀 2511·2019-08-29 16:12
閱讀 3386·2019-08-29 16:07
閱讀 665·2019-08-29 15:08