1. 前言
作為Android程序員,或者是想要去模仿一些酷炫的效果,或者是為了實(shí)現(xiàn)視覺的變態(tài)需求,或者是壓抑不住內(nèi)心的創(chuàng)造欲想要炫技,我們不可避免地需要做各種動畫。Android中,動畫主要分為幀動畫、插間動畫以及屬性動畫。幀動畫最為簡單,是用一系列的素材作為關(guān)鍵幀逐幀播放,常用于制作加載動畫,其工作量主要在設(shè)計(jì)部分;插間動畫與屬性動畫則更多地是需要開發(fā)通過控制各種動畫參數(shù)來實(shí)現(xiàn),只有系統(tǒng)地理解Android中動畫運(yùn)行的原理,才能創(chuàng)作出更出色的動畫,屬性動畫在下一篇文章中分析,本文主要分享我在探索插間動畫運(yùn)行原理過程中的一些收獲,包括:Matrix如何控制動畫參數(shù);動畫中各參數(shù)具體起什么作用;透明度動畫、縮放動畫、平移動畫以及旋轉(zhuǎn)動畫的運(yùn)行邏輯;動畫在View的繪制過程中如何被應(yīng)用。
2. Matrix介紹
在Android中,Matrix是一個(gè)3 x 3的矩陣:

Matrix可將一個(gè)點(diǎn)映射到另一個(gè)點(diǎn),矩陣中包含了處理縮放、透視以及平移的區(qū)域,從而可用于控制實(shí)現(xiàn)平移、縮放、旋轉(zhuǎn)等動畫效果。強(qiáng)烈建議閱讀Android Matrix理論與應(yīng)用詳解以更深入地了解Matrix實(shí)現(xiàn)動畫控制原理,這里僅摘錄其中的關(guān)鍵信息:
結(jié)論一:設(shè)對給定的圖像依次進(jìn)行了基本變化F1、F2、F3…..、Fn,它們的變化矩陣分別為T1、T2、T3…..、Tn,圖像復(fù)合變化的矩陣T可以表示為:T = TnTn-1…T1。
結(jié)論二:Preconcats matrix相當(dāng)于右乘矩陣,Postconcats matrix相當(dāng)于左乘矩陣。
Matrix還給我們提供了各種友好的接口來組合生成復(fù)雜的動畫,舉個(gè)例子:假如我們想要實(shí)現(xiàn)一個(gè)平移(a,b)之后旋轉(zhuǎn)(c,d)的動畫,那用Matrix的實(shí)現(xiàn)代碼就是這樣的:
Matrix matrix = new Matrix();
matrix.setTranslate(a, b);
matrix.postScale(c, d);
3. Animation運(yùn)行原理分析
(1)基本屬性介紹
使用過Animation的同學(xué)對下述基本屬性應(yīng)該非常熟悉,這里為了文章完整性,特地贅述一下:
- mStartTime:動畫實(shí)際開始時(shí)間
- mStartOffset:動畫延遲時(shí)間
- mFillEnabled:mFillBefore及mFillAfter是否使能
- mFillBefore:動畫結(jié)束之后是否需要進(jìn)行應(yīng)用動畫
- mFillAfter:動畫開始之前是否需要進(jìn)行應(yīng)用動畫
- mDuration:單次動畫運(yùn)行時(shí)長
- mRepeatMode:動畫重復(fù)模式(RESTART、REVERSE)
- mRepeatCount:動畫重復(fù)次數(shù)(INFINITE,直接值)
- mInterceptor:動畫插間器
- mListener:動畫開始、結(jié)束、重復(fù)回調(diào)監(jiān)聽器
雖然大部分都知道上面這些屬性怎么用,但是可能還是有一些人對這些字段為什么有這樣的作用不甚明白,于是我們就來分析一下。
(2)計(jì)算動畫數(shù)據(jù)
Animation在其getTransformation函數(shù)被調(diào)用時(shí)會計(jì)算一幀動畫數(shù)據(jù),而上面這些屬性基本都是在計(jì)算動畫數(shù)據(jù)時(shí)發(fā)光發(fā)熱,我們先看看getTransformation函數(shù)的運(yùn)行邏輯:
- 若
startTime為START_ON_FIRST_FRAME(值為-1)時(shí),將startTime設(shè)定為curTime - 計(jì)算當(dāng)前動畫進(jìn)度:
normalizedTime = (curTime - (startTime + startOffset))/duration - 若
mFillEnabled==false:將normalisedTime夾逼至[0.0f, 1.0f] - 判斷是否需要計(jì)算動畫數(shù)據(jù):
- 若
normalisedTime在[0.0f, 1.0f],需計(jì)算動畫數(shù)據(jù) - 若
normalisedTime不在[0.0f, 1.0f]:-
normalisedTime<0.0f, 僅當(dāng)mFillBefore==true時(shí)才計(jì)算動畫數(shù)據(jù) -
normalisedTime>1.0f, 僅當(dāng)mFillAfter==true時(shí)才計(jì)算動畫數(shù)據(jù)
-
- 若
- 若需需要計(jì)算動畫數(shù)據(jù):
- 若當(dāng)前為第一幀動畫,觸發(fā)
mListener.onAnimationStart - 若
mFillEnabled==false:將normalisedTime夾逼至[0.0f, 1.0f] - 根據(jù)插間器
mInterpolator調(diào)整動畫進(jìn)度:
interpolatedTime = mInterpolator.getInterpolation(normalizedTime) - 若動畫反轉(zhuǎn)標(biāo)志位
mCycleFlip為true,則
interpolatedTime = 1.0 - normalizedTime - 調(diào)用動畫更新函數(shù)
applyTransformation(interpolatedTime, transformation)計(jì)算出動畫數(shù)據(jù)
- 若當(dāng)前為第一幀動畫,觸發(fā)
- 若夾逼之前
normalisedTime大于1.0f, 則判斷是否需繼續(xù)執(zhí)行動畫:- 已執(zhí)行次數(shù)
mRepeatCount等于需執(zhí)行次數(shù)mRepeated- 若未觸發(fā)
mListener.onAnimationEnd,則觸發(fā)之
- 若未觸發(fā)
- 已執(zhí)行次數(shù)
mRepeatCount不等于需執(zhí)行次數(shù)mRepeated- 自增
mRepeatCount - 重置
mStartTime為-1 - 若
mRepeatMode為REVERSE,則取反mCycleFlip - 觸發(fā)
mListener.onAnimationRepeat
- 自增
- 已執(zhí)行次數(shù)
這一段是根據(jù)getTransformation源碼分析出來的,建議有興趣的同學(xué)可以直接查看源碼。上面這段分析留了一個(gè)不小的懸念,那就是動畫更新函數(shù)是什么鬼,這個(gè)函數(shù)在Animation這個(gè)抽象類中僅僅是個(gè)鉤子函數(shù),由其子類提供具體實(shí)現(xiàn),于是自然而然地引出了我們的下一個(gè)主題:主流動畫介紹。
(3)主流動畫分析
-
AlphaAnimation:透明度動畫
- 基本屬性
- mFromAlpha:起始透明度
- mToAlpha:終止透明度
- applyTransformation函數(shù)實(shí)現(xiàn)
- transformation.setAlpha(mFromAlpha + ((mToAlpha - mFromAlpha) * interpolatedTime))
- 基本屬性
-
ScaleAnimation:縮放動畫
- 基本屬性
- mFromX:起始X值
- mToX:終止X值
- mFromY:起始Y值
- mToY:終止Y值
- mPivotX:縮放中心點(diǎn)X坐標(biāo)
- mPivotY:縮放中心點(diǎn)Y坐標(biāo)
- 屬性計(jì)算邏輯
- mFromX、mToX、mFromY、mToY計(jì)算
- Float類型scale直接值
- Faction類型相對值
- 相對于自身(%):百分比轉(zhuǎn)換為float直接值
- 相對于父親(%p):根據(jù)父親size計(jì)算出size直接值,然后計(jì)算與本身size的百分比,最后轉(zhuǎn)換為float直接值
- Dimension類型size直接值:計(jì)算與本身size的百分比,然后轉(zhuǎn)換為float直接值
- mPivotX、mPivotY計(jì)算
- ABSOLUTE類型直接值
- RELATIVE_TO_SELF類型相對值:相對值乘以自身size得到直接值
- RELATIVE_TO_PARENT類型相對值:相對值乘以父親size得到直接值
- mFromX、mToX、mFromY、mToY計(jì)算
- applyTransformation函數(shù)實(shí)現(xiàn)
- sx = mFromX + ((mToX - mFromX) * interpolatedTime)
- sy = mFromY + ((mToY - mFromY) * interpolatedTime)
- 是否設(shè)定縮放中心點(diǎn):
- 若mPivotX==0 且 mPivotY==0:transformation.getMatrix().setScale(sx, sy)
- 否則:transformation.getMatrix().setScale(sx, sy, mPivotX, mPivotY)
- 基本屬性
-
TranslateAnimation:平移動畫
- 基本屬性
- mFromXDelta
- mToXDelta
- mFromYDelta
- mToYDelta
- 屬性計(jì)算邏輯
- 同ScaleAnimation中mPivotX、mPivotY的計(jì)算邏輯
- applyTransformation函數(shù)實(shí)現(xiàn)
- dx = mFromXDelta + ((mToXDelta - mFromXDelta) * interpolatedTime)
- dy = mFromYDelta + ((mToYDelta - mFromYDelta) * interpolatedTime)
- transformation.getMatrix().setTranslate(dx, dy)
- 基本屬性
-
RotateAnimation:旋轉(zhuǎn)動畫
- 基本屬性
- mFromDegrees
- mToDegrees
- mPivotX
- mPivotY
- 屬性計(jì)算邏輯
- mFromDegrees、mToDegrees均為角度(°)絕對值
- mPivotX、mPivotY計(jì)算邏輯同ScaleAnimation
- applyTransformation函數(shù)實(shí)現(xiàn)
- 是否設(shè)定縮放中心點(diǎn):
- 若mPivotX==0 且 mPivotY==0:transformation.getMatrix().setScale(sx, sy)
- 否則:transformation.getMatrix().setScale(sx, sy, mPivotX, mPivotY)
- 是否設(shè)定縮放中心點(diǎn):
- 基本屬性
透明度、縮放、平移以及旋轉(zhuǎn)是最基本的動畫,通過組合這些動畫可以實(shí)現(xiàn)各種不一樣的酷炫的效果,但是怎么才能實(shí)現(xiàn)這些動畫的組合,這就不得不提到AnimationSet了。
(4) AnimationSet分析
- AnimationSet是動畫集合,用于組合運(yùn)行多個(gè)動畫,僅支持playTogether模式。
- AnimationSet繼承了Animation的字段,但是字段的應(yīng)用有一些變化:
- duration, repeatMode, fillBefore, fillAfter:這些屬性會傳遞應(yīng)用到所有的子Animation
- repeatCount, fillEnabled:這些屬性在AnimationSet中不被應(yīng)用
- startOffset, shareInterpolator:這些屬性僅用于AnimationSet,不會傳遞至子Animation
- 4.0以前在xml中設(shè)置duration, repeatMode, fillBefore, fillAfter, startOffset不會被應(yīng)用,但是4.0之后再xml中設(shè)定這些屬性跟運(yùn)行時(shí)設(shè)定效果一致
- 一些值的計(jì)算邏輯:
- duration:
- 缺省時(shí),取所有子Animation中最長的duration;
- 已設(shè)定時(shí),返回mDuration
- hasAlpha、willChangeTransformationMatrix、willChangeBounds:當(dāng)有子Animation時(shí),所有子Animation的值取“或”
- startTime:取所有子Animation中最小的startTime
- 子Animation中startOffset處理:
- 保存子Animation的原始startOffset
- 設(shè)置子Animation的startOffset為原始startOffset與AnimationSet的startOffset之和
- 保存的原始startOffset在AnimationSet.clear是用于恢復(fù)各子Animation的startOffset
- duration:
- applyTransformation函數(shù)實(shí)現(xiàn)
- 順序調(diào)用子Animation的applyTransformation,然后利用Transformation.compose組合所有子Animation返回的Transformation作為該AnimationSet當(dāng)前幀的變換狀態(tài)
- started及more值取所有子Animation對應(yīng)值的“或”
- ended值取所有子Animation對應(yīng)值的“與”
- 當(dāng)started第一次為true時(shí),調(diào)用AnimationSet的mListener.onAnimationStart
- 當(dāng)ended第一次為true(此時(shí)所有子Animation均結(jié)束)時(shí),調(diào)用AnimationSet的mListener.onAnimationEnd
介紹完了主流動畫以及組合動畫,是不是Animation就介紹完了?其實(shí)不然,里面還漏掉了一個(gè)重要角色,那就是計(jì)算得到的動畫數(shù)據(jù)是用什么存儲的。實(shí)際上,Animation的動畫函數(shù)getTransformation目的在于生成當(dāng)前幀的一個(gè)Transformation,這個(gè)Transformation采用alpha以及Matrix存儲了一幀動畫的數(shù)據(jù),Transformation包含兩種模式:
- alpha模式:用于支持透明度動畫
- matrix模式:用于支持縮放、平移以及旋轉(zhuǎn)動畫
同時(shí),Transformation還提供了許多兩個(gè)接口用于組合多個(gè)Transformation:
- compose:前結(jié)合(alpha相乘、矩陣右乘、邊界疊加)
- postCompose:后結(jié)合(alpha相乘、矩陣左乘、邊界疊加)
至此,Animation本身算介紹完整了,還差一個(gè)可用于從XML中構(gòu)建動畫以及插間器的AnimationUtils,這里就不做具體分析了,有興趣的同學(xué)可以自行研究。但是,到現(xiàn)在為止,我們還沒講明白是:getTransformation這個(gè)函數(shù)究竟是在哪里調(diào)用的?計(jì)算得到的動畫數(shù)據(jù)又是怎么被應(yīng)用的?慌不要慌,待我娓娓道來,當(dāng)這些問題揭秘之后,我們就知道為什么Animation這個(gè)包要放在android.view下面以及Animation完成之后為什么View本身的屬性不會被改變,于是也就知道插間動畫(Animation)跟屬性動畫(Animator)本質(zhì)上的區(qū)別在哪了。
4. Animation的調(diào)用
要了解Animation的調(diào)用源頭,要從Animation的基本使用View.startAnimation開始尋根溯源:
public void startAnimation(Animation animation) {
animation.setStartTime(Animation.START_ON_FIRST_FRAME);
setAnimation(animation);
invalidateParentCaches();
invalidate(true);
}
通過invalidate(true)函數(shù)會觸發(fā)View的重新繪制,由于View的繪制流程并不是本文的重點(diǎn),因此這里僅說明從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就是Animation大顯神通的舞臺,其核心代碼主要分三個(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è)定動畫的初始區(qū)域,并在fillBefore為true時(shí)計(jì)算Animation動畫進(jìn)度為0.0f的數(shù)據(jù)
- 調(diào)用
調(diào)用
getTransformation根據(jù)當(dāng)前繪制事件生成Animation中對應(yīng)幀的動畫數(shù)據(jù)-
根據(jù)動畫數(shù)據(jù)設(shè)定重繪制區(qū)域
- 若僅為Alpha動畫,此時(shí)動畫區(qū)域?yàn)閂iew的當(dāng)前區(qū)域,且不會產(chǎn)生變化
- 若包含非Alpha動畫,此時(shí)動畫區(qū)域需要調(diào)用
Animation.getInvalidateRegion進(jìn)行計(jì)算,該函數(shù)會根據(jù)上述生成動畫數(shù)據(jù)Thransformation中的Matrix進(jìn)行計(jì)算,并與之前的動畫區(qū)域執(zhí)行unio操作,從而獲取動畫的完整區(qū)域 - 調(diào)用
ViewGroup.invalidate(int l, int t, int r, int b)設(shè)定繪制區(qū)域
當(dāng)View.applyLegacyAnimation調(diào)用完成之后,View此次繪制的動畫數(shù)據(jù)就構(gòu)建完成,之后便回到View.draw(Canvas, ViewGroup, long)應(yīng)用動畫數(shù)據(jù)對視圖進(jìn)行繪制刷新,其核心代碼如下:
if (transformToApply != null) {
if (concatMatrix) {
if (drawingWithRenderNode) {
// 應(yīng)用動畫數(shù)據(jù)
renderNode.setAnimationMatrix(transformToApply.getMatrix());
} else {
canvas.translate(-transX, -transY);
// 應(yīng)用動畫數(shù)據(jù)
canvas.concat(transformToApply.getMatrix());
canvas.translate(transX, transY);
}
parent.mGroupFlags |= ViewGroup.FLAG_CLEAR_TRANSFORMATION;
}
float transformAlpha = transformToApply.getAlpha();
if (transformAlpha < 1) {
// 應(yīng)用動畫數(shù)據(jù)
alpha *= transformAlpha;
parent.mGroupFlags |= ViewGroup.FLAG_CLEAR_TRANSFORMATION;
}
}
重點(diǎn)來了,大家看到Animation產(chǎn)生的動畫數(shù)據(jù)實(shí)際并不是應(yīng)用在View本身的,而是應(yīng)用在RenderNode或者Canvas上的,這就是為什么Animation不會改變View的屬性的根本所在。另一方面,我們知道Animation僅在View被繪制的時(shí)候才能發(fā)揮自己的價(jià)值,這也是為什么插間動畫被放在Android.view包內(nèi),因?yàn)樗鶹iew是真心相愛的。
文章到這,其實(shí)差不多可以結(jié)束了,但是創(chuàng)作動畫過程中總是會被用到的一個(gè)神器還沒出現(xiàn),這讓我有些不舍,盡管有太多人講解這一神器,但是我還是毅然決然地決定抄一遍書,一來表示我對這一神器的愛,另一方面也是希望讓文章更完整。
5. 插間器(Interpolator)
如果沒有插間器,Animation應(yīng)該按照時(shí)間來線性計(jì)算每一個(gè)時(shí)間點(diǎn)的動畫幀數(shù)據(jù);當(dāng)時(shí)當(dāng)加入插件器之后,我們計(jì)算動畫幀數(shù)據(jù)時(shí)就可以更加的富有創(chuàng)造力,我可以隨心所欲地計(jì)算任一時(shí)間點(diǎn)的動畫幀數(shù)據(jù),可以新加速在減速,也可以先減速在加速,總之一句話,我的地盤我做主。按照劇情的發(fā)展,接下來我應(yīng)該介紹常用插間器了,但是作為一個(gè)有態(tài)度的程序員,我是不會按常理出牌的,想要了解常用插間器的實(shí)現(xiàn)原理,建議閱讀Android Animations Tutorial 5: More on Interpolators。
6. 后記
其實(shí)很早之前就看過Animation的源碼,但是當(dāng)時(shí)因?yàn)閼胁]有寫文章做筆記,這次因?yàn)轫?xiàng)目需要優(yōu)化動畫,于是又重新擼了一遍,在此撰文為記,以備后用。當(dāng)然,也希望這篇分享能給大家一些收獲,非常感謝你的閱讀,如果有浪費(fèi)到你的時(shí)間,也就浪費(fèi)了,權(quán)當(dāng)看了一章湊字?jǐn)?shù)的小說,233333~~~