Android動(dòng)畫的使用

? ? ? ?在APP開發(fā)的過程中,在合適的時(shí)機(jī)引入合適的動(dòng)畫。會(huì)讓我們的APP動(dòng)起來,更加的吸引眼球。這里我們就來總結(jié)下Android里面的動(dòng)畫。按照我們的理解Android里面動(dòng)畫分為三類:屬性動(dòng)畫(Property Animation)、視圖動(dòng)畫(View Animation)、過渡動(dòng)畫(Transition Animation)。

? ? ? ?在說Android的這三種動(dòng)畫之前,我們先的來提一下TimeInterpolator插值器。我們可以把所有的動(dòng)畫簡單的認(rèn)為是:在指定的時(shí)間內(nèi),從第一種狀態(tài)動(dòng)態(tài)的過渡到第二種狀態(tài)的一個(gè)過程。那在這個(gè)持續(xù)時(shí)間內(nèi)動(dòng)畫的執(zhí)行過程中是怎么個(gè)變化的呢,線性變換,加速變換還是其他的什么變換。所以這里就會(huì)引入一個(gè)插值器的概念。插值器改變的就是不同時(shí)間進(jìn)度上的值,雖然時(shí)間的流逝是線性的(速度是不變),但是插值器可以通過改變不同時(shí)間上對(duì)應(yīng)的動(dòng)畫的值,來達(dá)到各種各樣的變換效果。Android系統(tǒng)也給默認(rèn)提供了九種插值器的效果。當(dāng)然也可以去自定義一個(gè)插值器。

系統(tǒng)提供的九種TimeInterpolator插值器

Interpolator Class xml Resource ID Description 數(shù)學(xué)建模
LinearInterpolator @android:anim/linear_interpolator 線性插值器
LinearInterpolator
AccelerateInterpolator @android:anim/accelerate_interpolator 加速度插值器
AccelerateInterpolator
DecelerateInterpolator @android:anim/decelerate_interpolator 減速插值器
DecelerateInterpolator
AccelerateDecelerateInterpolator @android:anim/accelerate_decelerate_interpolator 先加速后減速插值器
AccelerateDecelerateInterpolator
AnticipateInterpolator @android:anim/anticipate_interpolator 在開始的時(shí)候向后然后向前甩
AnticipateInterpolator
OvershootInterpolator @android:anim/overshoot_interpolator 向前甩一定值后再回到原來位置
OvershootInterpolator
AnticipateOvershootInterpolator @android:anim/anticipate_overshoot_interpolator 開始的時(shí)候向后然后向前甩一定值后返回最后的值
AnticipateOvershootInterpolator
BounceInterpolator @android:anim/bounce_interpolator 動(dòng)畫結(jié)束的時(shí)候彈起
BounceInterpolator
CycleInterpolator @android:anim/cycle_interpolator 動(dòng)畫循環(huán)播放特定的次數(shù),速率改變沿著正弦曲線
CycleInterpolator

一、屬性動(dòng)畫(Property Animation)

? ? ? ?屬性動(dòng)畫就是在動(dòng)畫的過程中通過改變對(duì)象的屬性來達(dá)到動(dòng)態(tài)效果。這里的對(duì)象不局限于視圖View,可以是任何你想要的對(duì)象。屬性動(dòng)畫也是我們目前使用的最多的一種動(dòng)畫。

關(guān)于對(duì)象的屬性可以把屬性簡單的理解為對(duì)象的參數(shù)。

1.1、屬性動(dòng)畫源碼過程分析

? ? ? ?我們站在源代碼的角度上來看屬性動(dòng)畫。和屬性動(dòng)畫相關(guān)的類有:AnimatorSet、ObjectAnimator、ValueAnimator、TimeInterpolator插值器、TypeEvaluator估值器。

? ? ? ?因?yàn)榍岸螘r(shí)間通過源碼走讀的方式已經(jīng)對(duì)屬性動(dòng)畫的過程做了一個(gè)簡單的分析,所以上面我很多地方我就直接貼了鏈接地址。如果大家在看的過程中有什么疑問可以留言,在我的能力范圍會(huì)為大家解答的。

1.2、屬性動(dòng)畫的使用

? ? ? ?屬性動(dòng)畫的使用,一般可以分為以下幾個(gè)步驟:

  1. 確定屬性動(dòng)畫作用的對(duì)象。對(duì)象可以是View也可以是其他任何對(duì)象。

  2. 確定屬性動(dòng)畫作用對(duì)象屬性,和屬性值的變化范圍。

  3. 定義屬性動(dòng)畫:AnimatorSet、ObjectAnimator、ValueAnimator。

  4. 選擇合適的插值器。(從系統(tǒng)給提供的九種插值器里面選擇或者直接自定義一個(gè)插值器)。

  5. 選擇合適的估值器。(如果我們屬性的參數(shù)是int,float以外的時(shí)候,可能就需要我們自定義估值器了)。

? ? ? ?關(guān)于插值器和估值器的使用大家可以參考下Android動(dòng)畫TimeInterpolator(插值器)和TypeEvaluator(估值器)分析。里面有一個(gè)非常簡單的自定義插值器的實(shí)例。這里我們重點(diǎn)瞧一瞧第三點(diǎn)定義屬性動(dòng)畫。和所有的動(dòng)畫一樣屬性動(dòng)畫的定義也有兩種方式:XML文件的方式、JAVA代碼的方式。

1.2.1、XML文件定義屬性動(dòng)畫

? ? ? ?XML屬性動(dòng)畫資源文件建議放在 res/animation 文件夾下(也可以放置在res/anim下)。XML屬性動(dòng)畫資源文件相關(guān)屬性如下:

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
    <!-- 動(dòng)畫的執(zhí)行方式;sequentially:順序執(zhí)行、together:同時(shí)執(zhí)行 -->
    android:ordering="sequentially | together">

    <!-- 指定了屬性的屬性動(dòng)畫-->
    <objectAnimator
        <!-- 屬性,一般在View里面有對(duì)應(yīng)的 setXX() getXX()的方法 -->
        android:propertyName="string"
        <!-- 動(dòng)畫持續(xù)時(shí)間,默認(rèn)300毫秒 -->
        android:duration="int"
        <!-- 動(dòng)畫的開始值 -->
        android:valueFrom="float | int | color"
        <!-- 動(dòng)畫的結(jié)束值 -->
        android:valueTo="float | int | color"
        <!-- 動(dòng)畫啟動(dòng)延時(shí)時(shí)間,單位毫秒 -->
        android:startOffset="int"
        <!-- 動(dòng)畫重復(fù)次數(shù) -->
        android:repeatCount="int"
        <!-- 動(dòng)畫重復(fù)的模式;repeat:重復(fù)播放、 reverse:反向播放-->
        android:repeatMode=["repeat" | "reverse"]
        <!-- 動(dòng)畫值的類型-->
        android:valueType=["intType" | "floatType"]
        <!-- 動(dòng)畫插值器-->
        android:interpolator=[res anim]/>

    <!-- 屬性動(dòng)畫沒有指定屬性,要自己去處理回調(diào) -->
    <animator
        android:duration="int"
        android:valueFrom="float | int | color"
        android:valueTo="float | int | color"
        android:startOffset="int"
        android:repeatCount="int"
        android:repeatMode=["repeat" | "reverse"]
        android:valueType=["intType" | "floatType"]/>

    <set>
    ...
    </set>
</set>

? ? ? ?XML定義屬性動(dòng)畫,具體實(shí)例:

第一步,創(chuàng)建一個(gè)XML屬性動(dòng)畫資源文件(res/animation目錄下)

<set xmlns:android="http://schemas.android.com/apk/res/android"
    android:ordering="together">
    <set android:ordering="together">
        <objectAnimator
            android:propertyName="rotation"
            android:duration="10000"
            android:valueFrom="0f"
            android:valueTo="360f"
            android:valueType="floatType"
            android:interpolator="@android:anim/accelerate_interpolator"/>
    </set>
    <objectAnimator
        android:propertyName="alpha"
        android:duration="10000"
        android:valueFrom="0"
        android:valueTo="1f"
        android:valueType="floatType" />
</set>

第二步,啟動(dòng)資源文件動(dòng)畫

    AnimatorSet animationSet = (AnimatorSet) AnimatorInflater.loadAnimator(mContext, R.animator.property_set);
    animationSet.setTarget(mImageView);
    animationSet.start();

? ? ? ?到這里XML定義的屬性動(dòng)畫就播放出來,最關(guān)鍵的地方還是XML屬性動(dòng)畫文件的配置。

1.2.2、JAVA方式定義屬性動(dòng)畫

? ? ? ?JAVA方式定義屬性動(dòng)畫可能關(guān)鍵的部分還是的知道AnimatorSet、ObjectAnimator、ValueAnimator這幾個(gè)類里面動(dòng)畫參數(shù)設(shè)置的一些API了。和上面提到的XML屬性動(dòng)畫資源文件相關(guān)屬性是一一對(duì)應(yīng)的,肯定有相關(guān)的setXX() getXX()方法的。接下來我們通過一個(gè)簡單的實(shí)例來瞧一瞧JAVA方式定義和播放屬性動(dòng)畫。

    AnimatorSet animatorSet = new AnimatorSet();
    ObjectAnimator objectXAnimator = ObjectAnimator.ofFloat(mImageView, "rotation", 0f, 360f);
    objectXAnimator.setDuration(10000);
    animatorSet.playTogether(objectXAnimator);
    ObjectAnimator objectAlphaAnimator = ObjectAnimator.ofFloat(mImageView, "alpha", 0f, 1f);
    objectAlphaAnimator.setDuration(10000);
    AnimatorSet animatorSetResult = new AnimatorSet();
    animatorSetResult.playTogether(animatorSet, objectAlphaAnimator);
    animatorSetResult.start();

二、視圖動(dòng)畫(View Animation)

? ? ? ?視圖動(dòng)畫(View Animation),通過確定視圖開始時(shí)候的樣式和視圖結(jié)束時(shí)候的樣式、中間所有動(dòng)畫變化過程則由系統(tǒng)補(bǔ)全的動(dòng)畫效果。視圖動(dòng)畫分為兩類:補(bǔ)間動(dòng)畫(Tween animation)、幀動(dòng)畫(Frame animation)兩種。

這里明確的指定了視圖動(dòng)畫的作用是視圖(View,View的子類)。所以視圖動(dòng)畫只對(duì)View或者View的子類有作用。

2.1、補(bǔ)間動(dòng)畫(Tween animation)

? ? ? ?所有補(bǔ)間動(dòng)畫的基類都是Animation,而且所有補(bǔ)間動(dòng)畫的變換過程都是圍繞Matrix做操作,Matrix是一個(gè)3*3的變形矩陣。通過高等數(shù)學(xué)里面矩陣的運(yùn)算公式做平移,縮放,斜切,旋轉(zhuǎn)等變換。簡單來說就是在動(dòng)畫過程中隨著時(shí)間的推移Matrix通過某種變換(平移,縮放,斜切,旋轉(zhuǎn)等)轉(zhuǎn)換到目標(biāo)Matrix。然后把Matrix的變化作用在視圖上。

2.1.1、補(bǔ)間動(dòng)畫源碼簡單分析

? ? ? ?雖然補(bǔ)間動(dòng)畫的過程是通過View自動(dòng)補(bǔ)全的,但是咱們還是想知道系統(tǒng)里面處理補(bǔ)間動(dòng)畫的大概流程。雖然不可能全部知道,但是知道個(gè)大概的流程總歸是好的。所有補(bǔ)間動(dòng)畫開始的動(dòng)作都是從View里面的startAnimation()函數(shù)開始的。

    public void startAnimation(Animation animation) {
        animation.setStartTime(Animation.START_ON_FIRST_FRAME);
        setAnimation(animation);
        invalidateParentCaches();
        invalidate(true);
    }

很顯然startAnimation()函數(shù)的調(diào)用會(huì)讓View重繪,之后就會(huì)調(diào)用到draw()函數(shù)了,我們直接看帶有三個(gè)參數(shù)的的draw()函數(shù)

    boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) {
        ......
        final Animation a = getAnimation();//如果有設(shè)置補(bǔ)間動(dòng)畫
        if (a != null) {
            //applyLegacyAnimation函數(shù)會(huì)把每次animation的變化都存到parent.getChildTransformation()里面
            more = applyLegacyAnimation(parent, drawingTime, a, scalingRequired);
            concatMatrix = a.willChangeTransformationMatrix();
            if (concatMatrix) {
                mPrivateFlags3 |= PFLAG3_VIEW_IS_ANIMATING_TRANSFORM;
            }
            //獲取進(jìn)過applyLegacyAnimation變換之后的Matrix
            transformToApply = parent.getChildTransformation();
        } else {
            ......
        }
        ......
        if (!drawingWithRenderNode || transformToApply != null) {
            restoreTo = canvas.save();
        }
        ......
        float alpha = drawingWithRenderNode ? 1 : (getAlpha() * getTransitionAlpha());
        if (transformToApply != null
            || alpha < 1
            || !hasIdentityMatrix()
            || (mPrivateFlags3 & PFLAG3_VIEW_IS_ANIMATING_ALPHA) != 0) {
            if (transformToApply != null || !childHasIdentityMatrix) {
                ......
                if (transformToApply != null) {
                    if (concatMatrix) {
                        if (drawingWithRenderNode) {
                            renderNode.setAnimationMatrix(transformToApply.getMatrix());
                        } else {
                            // Undo the scroll translation, apply the transformation matrix,
                            // then redo the scroll translate to get the correct result.
                            canvas.translate(-transX, -transY);
                            //把變換的結(jié)果應(yīng)用到canvas上,這樣繪制出來的視圖就是動(dòng)畫變換之后的結(jié)果了
                            canvas.concat(transformToApply.getMatrix());
                            canvas.translate(transX, transY);
                        }
                        parent.mGroupFlags |= ViewGroup.FLAG_CLEAR_TRANSFORMATION;
                    }

                    float transformAlpha = transformToApply.getAlpha();
                    if (transformAlpha < 1) {
                        alpha *= transformAlpha;
                        parent.mGroupFlags |= ViewGroup.FLAG_CLEAR_TRANSFORMATION;
                    }
                }
                ......
            }
        } else if ((mPrivateFlags & PFLAG_ALPHA_SET) == PFLAG_ALPHA_SET) {
            ......
        }
        if (restoreTo >= 0) {
            canvas.restoreToCount(restoreTo);
        }
        ......
    }
    private boolean applyLegacyAnimation(ViewGroup parent, long drawingTime,
                                         Animation a, boolean scalingRequired) {
        ......
        //動(dòng)畫變化過程中,變換的結(jié)果要保存的變量
        final Transformation t = parent.getChildTransformation();
        //動(dòng)畫變化更新Matrix結(jié)果,這樣parent.getChildTransformation()里面保存的就是變換之后的結(jié)果
        boolean more = a.getTransformation(drawingTime, t, 1f);
        ......
    }

關(guān)鍵在more = applyLegacyAnimation(parent, drawingTime, a, scalingRequired);一句的調(diào)用,調(diào)用這句后動(dòng)畫變換之后的結(jié)果會(huì)保存在parent.getChildTransformation()里面,之后在通過canvas.concat(transformToApply.getMatrix());把變換的結(jié)果運(yùn)用到視圖Cavans上來,產(chǎn)生相應(yīng)的動(dòng)畫效果。

上面對(duì)補(bǔ)間動(dòng)畫的實(shí)現(xiàn)做了一個(gè)非常非常簡單的分析,只是摘出了關(guān)鍵的部分。如果想了解更加詳細(xì)的細(xì)節(jié)就得去好好的啃源碼了。

2.1.2、補(bǔ)間動(dòng)畫的使用

? ? ? ?補(bǔ)間動(dòng)畫的使用,三個(gè)步驟:

  1. 創(chuàng)建補(bǔ)間動(dòng)畫。

  2. 執(zhí)行補(bǔ)間動(dòng)畫。

  3. 監(jiān)聽補(bǔ)間動(dòng)畫的執(zhí)行過程(AnimationListener)。

同樣和其它的動(dòng)畫一樣補(bǔ)間動(dòng)畫也可以通過XML(res/anim目錄下)、JAVA兩種方式來創(chuàng)建。

? ? ? ?所有補(bǔ)間動(dòng)畫都有一個(gè)共同的基類,所以他們有一些公共的屬性:

    android:duration="2000"<!-- 動(dòng)畫持續(xù)時(shí)間 -->
    android:repeatMode="restart"<!-- 動(dòng)畫重復(fù)模式 -->
    android:repeatCount="3"<!-- 動(dòng)畫播放次數(shù) -->
    android:fillAfter="true"<!-- 動(dòng)畫結(jié)束時(shí),停留在最后一幀 -->
    android:interpolator="@android:anim/decelerate_interpolator"<!-- 插值器 -->

(這里只是列出XML里面的設(shè)置方式,JAVA里面也是一樣的,都有相應(yīng)的setXX() getXX()方法)

? ? ? ?Android系統(tǒng)已經(jīng)默認(rèn)給咱提供了五種補(bǔ)間動(dòng)畫。大部分情況下這五種補(bǔ)間動(dòng)畫都能滿足我們的需求。

名稱 原理 對(duì)應(yīng)Animation子類
平移動(dòng)畫(Transition) 移動(dòng)視圖的位置 TranslateAnimation
縮放動(dòng)畫(Scale) 放大/縮小 視圖的大小 ScaleAnimation
旋轉(zhuǎn)動(dòng)畫(Rotate) 旋轉(zhuǎn)視圖的角度 RotateAnimation
透明度動(dòng)畫(Alpha) 改變視圖的透明度 AlphaAnimation
組合動(dòng)畫(Set) 把多種動(dòng)畫組合在一起執(zhí)行 AnimationSet
2.1.2.1、平移動(dòng)畫(Transition)TranslateAnimation

? ? ? ?改變View的位置。

<?xml version="1.0" encoding="utf-8"?>
<translate xmlns:android="http://schemas.android.com/apk/res/android"
    android:fromXDelta=""<!-- 動(dòng)畫開始時(shí)view左上角x坐標(biāo)的位置 -->
    android:fromYDelta=""<!-- 動(dòng)畫開始時(shí)view左上角坐標(biāo)的位置 -->
    android:toXDelta=""<!-- 動(dòng)畫結(jié)束時(shí)view左上角x坐標(biāo)的位置 -->
    android:toYDelta=""<!-- 動(dòng)畫結(jié)束時(shí)view左上角y坐標(biāo)的位置 -->
    />

補(bǔ)間動(dòng)畫里面所有距離的設(shè)置都三種形式:具體的值、值%、值%p。其中值%表示相對(duì)自身寬度或者高度的多少,值%p則是相對(duì)父控件寬度或者高度的多少。

2.1.2.2、縮放動(dòng)畫(Scale) ScaleAnimation

? ? ? ?控制View的縮放。

<?xml version="1.0" encoding="utf-8"?>
<scale xmlns:android="http://schemas.android.com/apk/res/android"
    android:pivotY=""<!-- 縮放的中心點(diǎn)x -->
    android:pivotX=""<!-- 縮放的中心點(diǎn)y -->
    android:fromXScale=""<!-- x方向縮放的開始值(1表示不縮放) -->
    android:fromYScale=""<!-- y方向縮放的開始值(1表示不縮放) -->
    android:toXScale=""<!-- x方向縮放的結(jié)束值(1表示不縮放) -->
    android:toYScale=""<!-- y方向縮放的結(jié)束值(1表示不縮放) -->
    />

2.1.2.3、旋轉(zhuǎn)動(dòng)畫(Rotate) RotateAnimation

? ? ? ?控制View的旋轉(zhuǎn)。

<?xml version="1.0" encoding="utf-8"?>
<rotate xmlns:android="http://schemas.android.com/apk/res/android"
    android:fromDegrees=""<!-- 開始角度 -->
    android:pivotX=""<!-- 旋轉(zhuǎn)中心點(diǎn)x -->
    android:pivotY=""<!-- 旋轉(zhuǎn)中心點(diǎn)y -->
    android:toDegrees=""<!-- 結(jié)束角度 -->
    />
2.1.2.4、透明度動(dòng)畫(Alpha) AlphaAnimation

? ? ? ?控制View的透明度。

<?xml version="1.0" encoding="utf-8"?>
<alpha xmlns:android="http://schemas.android.com/apk/res/android"
    android:fromAlpha=""<!-- 動(dòng)畫開始透明度 -->
    android:toAlpha=""<!-- 動(dòng)畫介紹透明度 -->
    />

2.1.2.5、組合動(dòng)畫(Set) AnimationSet

? ? ? ?多個(gè)動(dòng)畫組合起來對(duì)View起作用。

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
    android:interpolator=""<!-- 插值器 -->
    android:shareInterpolator=""<!-- 是否共享插值起 -->
    >

</set>

關(guān)于屬性動(dòng)畫的具體實(shí)例,這里我們就沒有列出來了,在最下面DEMO里面有對(duì)應(yīng)的實(shí)例。

2.2、幀動(dòng)畫(Frame animation)

? ? ? ?幀動(dòng)畫,就是由一幀幀的圖片組合出來。通過指定圖片展示的順序,達(dá)到動(dòng)畫的展示的動(dòng)態(tài)效果。換句話說就是在動(dòng)畫的過程中替換視圖的背景。

2.2.1、幀動(dòng)畫源碼簡單分析

? ? ? ?幀動(dòng)畫對(duì)應(yīng)的視圖背景類是AnimationDrawable。我們先看下幀動(dòng)畫是怎么播放的,然后在看下通過布局文件里面設(shè)置android:src或者android:background是怎么解析為AnimationDrawable對(duì)象的。

2.2.1.1、AnimationDrawable播放動(dòng)畫簡單分析

? ? ? ?幀動(dòng)畫的播放是通過調(diào)用AnimationDrawable類的start()函數(shù)開始的。

AnimationDrawable類

    public void start() {
        ......
        if (!isRunning()) {
            // Start from 0th frame.
            //從第一幀開始播放動(dòng)畫
            setFrame(0, false, mAnimationState.getChildCount() > 1
                               || !mAnimationState.mOneShot);
        }
    }

    private void setFrame(int frame, boolean unschedule, boolean animate) {
        ......
        //這里就把當(dāng)前幀對(duì)應(yīng)的圖片給了對(duì)應(yīng)的視圖
        selectDrawable(frame);
        ......
        if (animate) {
            ......
            //很顯然,定時(shí)任務(wù),指定時(shí)間之后在調(diào)用this里面的run函數(shù)。
            scheduleSelf(this, SystemClock.uptimeMillis() + mAnimationState.mDurations[frame]);
        }
    }

start()函數(shù)里面會(huì)調(diào)用setFrame()函數(shù),setFrame()從字面意思也能看出來應(yīng)該是去設(shè)置對(duì)應(yīng)的幀

AnimationDrawable類

    private void setFrame(int frame, boolean unschedule, boolean animate) {
        ......
        //這里就把當(dāng)前幀對(duì)應(yīng)的圖片給了對(duì)應(yīng)的視圖
        selectDrawable(frame);
        ......
        if (animate) {
            ......
            //很顯然,定時(shí)任務(wù),指定時(shí)間之后在調(diào)用this里面的run函數(shù)。
            scheduleSelf(this, SystemClock.uptimeMillis() + mAnimationState.mDurations[frame]);
        }
    }

selectDrawable(frame);的調(diào)用就把當(dāng)前視圖對(duì)應(yīng)的背景圖片給換掉了,selectDrawable()函數(shù)的調(diào)用會(huì)讓DrawableContainer去重繪,調(diào)用DrawableContainer里面的draw()函數(shù),在draw()函數(shù)里面替換幀對(duì)應(yīng)的圖片。然后scheduleSelf()函數(shù)相當(dāng)于啟動(dòng)了一個(gè)定時(shí)任務(wù)。我們找到對(duì)應(yīng)run()函數(shù)。

AnimationDrawable類

    @Override
    public void run() {
        nextFrame(false);
    }

    private void nextFrame(boolean unschedule) {
        //下一幀
        int nextFrame = mCurFrame + 1;
        ......
        //又回到了setFrame函數(shù)
        setFrame(nextFrame, unschedule, !isLastFrame);
    }

我們往最簡單的說AnimationDrawable播放動(dòng)畫就是設(shè)置一個(gè)幀之后啟動(dòng)一個(gè)定時(shí)任務(wù)然后去設(shè)置下一個(gè)幀。

2.2.1.2、android:src或者android:background怎么解析為AnimationDrawable對(duì)象

? ? ? ?為了分析幀動(dòng)畫XML資源文件是怎么解析成AnimationDrawable對(duì)象的。咱先找一個(gè)入口,咱們就從View類的setBackgroundResource(id)開始。

View

    @RemotableViewMethod
    public void setBackgroundResource(@DrawableRes int resid) {
        ......
        Drawable d = null;
        if (resid != 0) {
            //通過資源id,解析到對(duì)應(yīng)的Drawable
            d = mContext.getDrawable(resid);
        }
        ......
    }

這樣就直接到Context里面的getDrawable()函數(shù)了,-> Resources類的getDrawable()函數(shù)了,->Resources類 getDrawableForDensity()函數(shù),->ResourcesImpl類的loadDrawable()

ResourcesImpl

    @Nullable
    Drawable loadDrawable(@NonNull Resources wrapper, @NonNull TypedValue value, int id,
                          int density, @Nullable Resources.Theme theme)
        throws Resources.NotFoundException {
            ......
            boolean needsNewDrawableAfterCache = false;
            if (cs != null) {
                dr = cs.newDrawable(wrapper);
            } else if (isColorDrawable) {
                dr = new ColorDrawable(value.data);
            } else {
                //解析xml,咱們從這里跟進(jìn)去
                dr = loadDrawableForCookie(wrapper, value, id, density, null);
            }
            ......
    }

關(guān)鍵調(diào)用在loadDrawableForCookie()函數(shù)

ResourcesImpl

    private Drawable loadDrawableForCookie(@NonNull Resources wrapper, @NonNull TypedValue value,
                                           int id, int density, @Nullable Resources.Theme theme) {
        ......
        final String file = value.string.toString();
        ......
        final Drawable dr;

        Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, file);
        try {
            if (file.endsWith(".xml")) {
                //從xml文件解析資源文件
                final XmlResourceParser rp = loadXmlResourceParser(
                    file, id, value.assetCookie, "drawable");
                dr = Drawable.createFromXmlForDensity(wrapper, rp, density, theme);
                rp.close();
            } else {
                ......
            }
        } catch (Exception e) {
            ......
        }
        ......
    }

這里關(guān)鍵在Drawable.createFromXmlForDensity()調(diào)用

Drawable

    public static Drawable createFromXmlForDensity(@NonNull Resources r,
                                                   @NonNull XmlPullParser parser, int density, @Nullable Resources.Theme theme)
        throws XmlPullParserException, IOException {
        ......
        Drawable drawable = createFromXmlInnerForDensity(r, parser, attrs, density, theme);
        ......
    }

    static Drawable createFromXmlInnerForDensity(@NonNull Resources r,
                                                 @NonNull XmlPullParser parser, @NonNull AttributeSet attrs, int density,
                                                 @Nullable Resources.Theme theme) throws XmlPullParserException, IOException {
        //r.getDrawableInflater()就是DrawableInflater類
        return r.getDrawableInflater().inflateFromXmlForDensity(parser.getName(), parser, attrs,
                                                                density, theme);
    }

DrawableInflater

    @NonNull
    Drawable inflateFromXmlForDensity(@NonNull String name, @NonNull XmlPullParser parser,
                                      @NonNull AttributeSet attrs, int density, @Nullable Resources.Theme theme)
        throws XmlPullParserException, IOException {
        ......
        //通過tag name得到各種Drawable對(duì)應(yīng)的對(duì)象,比如我們這里是"animation-list" 得到AnimationDrawable
        Drawable drawable = inflateFromTag(name);
        if (drawable == null) {
            drawable = inflateFromClass(name);
        }
        drawable.setSrcDensityOverride(density);
        //進(jìn)入到各種Drawable對(duì)應(yīng)的對(duì)象的inflate解析里面去,比如我們這里是進(jìn)入AnimationDrawable的inflate解析里面去
        drawable.inflate(mRes, parser, attrs, theme);
        ......
    }

這里通過XML文件的tag名字找到對(duì)應(yīng)的Drawable對(duì)象,我們這里分析的是幀動(dòng)畫,對(duì)應(yīng)的tag是animation-list所以對(duì)應(yīng)的Drawable對(duì)象是AnimationDrawable,這樣所有的解析工作就都過渡到AnimationDrawable類里面去了,最終在AnimationDrawable類的inflate()函數(shù)里面解析出每一幀具體的數(shù)據(jù)。到此幀動(dòng)畫xml的解析就結(jié)束了,剩下的就是自己調(diào)用start()函數(shù)啟動(dòng)動(dòng)畫。

上面的流程有很多地方都沒有深究進(jìn)去,看源碼。在沒有特殊要求的情況下,我都是看一個(gè)大概的流程。做到心里有數(shù)。

2.2.2、幀動(dòng)畫的簡單使用

? ? ? ?所有的動(dòng)畫都一樣,可以通過XML、JAVA兩種方式來定義動(dòng)畫對(duì)象。

2.2.2.1、XML定義幀動(dòng)畫

? ? ? ?XML設(shè)置幀動(dòng)畫的資源文件 res/drawable文件夾目錄下.

<?xml version="1.0" encoding="utf-8"?>
<animation-list xmlns:android="http://schemas.android.com/apk/res/android"
    <!-- 是否連續(xù)播放 -->
    android:oneshot=["true" | "false"] >
    <item
        <!-- 每一幀的圖片 -->
        android:drawable="@[package:]drawable/drawable_resource_name"
        <!-- 每一幀的持續(xù)時(shí)間 -->
        android:duration="integer" />
</animation-list>

一個(gè)簡單的XML定義幀動(dòng)畫的實(shí)例

<?xml version="1.0" encoding="utf-8"?>
<animation-list xmlns:android="http://schemas.android.com/apk/res/android"
    android:oneshot="false">
    <item
        android:drawable="@mipmap/img_miao1"
        android:duration="80" />
    <item
        android:drawable="@mipmap/img_miao2"
        android:duration="80" />
    <item
        android:drawable="@mipmap/img_miao3"
        android:duration="80" />
</animation-list>

? ? ? ?播放動(dòng)畫

AnimationDrawable animationDrawable = (AnimationDrawable) mImageAnimation.getDrawable();
                animationDrawable.start();
2.2.2.2、JAVA定義幀動(dòng)畫

? ? ? ?通過JAVA的方式來定義幀動(dòng)畫這個(gè)就簡單的多了。我們直接用一個(gè)簡單的實(shí)例來說明。

    AnimationDrawable animationDrawable = new AnimationDrawable();
    animationDrawable.addFrame(getResources().getDrawable(R.mipmap.img_miao1), 80);
    animationDrawable.addFrame(getResources().getDrawable(R.mipmap.img_miao2), 80);
    animationDrawable.addFrame(getResources().getDrawable(R.mipmap.img_miao3), 80);
    animationDrawable.setOneShot(false);
    mImageAnimation.setBackgroundDrawable(animationDrawable);
    animationDrawable.start();

? ? ? ?按照上面的方式實(shí)現(xiàn)幀動(dòng)畫,使用不當(dāng)容易發(fā)生OOM的情況,而且效率比較低。給大家推薦YY公司出的一個(gè)實(shí)現(xiàn)幀動(dòng)畫的開源庫SVGA Animation SVGA Animation。提供了高性能動(dòng)畫播放體驗(yàn)。同時(shí)SVGA是一種同時(shí)兼容 iOS / Android / Web 多個(gè)平臺(tái)的動(dòng)畫格式。

三、過渡動(dòng)畫(Transition Animation)

? ? ? ?在Android 4.4 Transition 就已經(jīng)引入了,但在Android 5.0(API 21)之后,Transition 被更多的應(yīng)用起來。相對(duì)于View Animation或Property Animator,Transition動(dòng)畫更加具有特殊性,Transition可以看作對(duì)Property Animator的高度封裝。不同于Animator,Transition動(dòng)畫具有視覺連續(xù)性的場景切換。

? ? ? ?關(guān)于過渡動(dòng)畫(Transition Animation)更加詳細(xì)的內(nèi)容,我就偷個(gè)懶,請看之前寫的一篇Android Transition(Android過渡動(dòng)畫)的介紹。

? ? ? ?到此三種動(dòng)畫,我們都做了一個(gè)非常簡單的介紹。最后給出文章里面對(duì)應(yīng)的一些DEMO實(shí)例的下載地址。DEMO

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

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

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