Android Animation運(yùn)行原理詳解

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 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)行邏輯:

  1. startTimeSTART_ON_FIRST_FRAME(值為-1)時(shí),將startTime設(shè)定為curTime
  2. 計(jì)算當(dāng)前動畫進(jìn)度:
    normalizedTime = (curTime - (startTime + startOffset))/duration
  3. mFillEnabled==false:將normalisedTime夾逼至[0.0f, 1.0f]
  4. 判斷是否需要計(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ù)
  5. 若需需要計(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)志位mCycleFliptrue,則
      interpolatedTime = 1.0 - normalizedTime
    • 調(diào)用動畫更新函數(shù)applyTransformation(interpolatedTime, transformation)計(jì)算出動畫數(shù)據(jù)
  6. 若夾逼之前normalisedTime大于1.0f, 則判斷是否需繼續(xù)執(zhí)行動畫:
    • 已執(zhí)行次數(shù)mRepeatCount等于需執(zhí)行次數(shù)mRepeated
      • 若未觸發(fā)mListener.onAnimationEnd,則觸發(fā)之
    • 已執(zhí)行次數(shù)mRepeatCount不等于需執(zhí)行次數(shù)mRepeated
      • 自增mRepeatCount
      • 重置mStartTime為-1
      • mRepeatModeREVERSE,則取反mCycleFlip
      • 觸發(fā)mListener.onAnimationRepeat

這一段是根據(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得到直接值
    • 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)

透明度、縮放、平移以及旋轉(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
  • 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è)部分:

  1. 初始化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ù)
  2. 調(diào)用getTransformation根據(jù)當(dāng)前繪制事件生成Animation中對應(yīng)幀的動畫數(shù)據(jù)

  3. 根據(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~~~

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容