Android動(dòng)畫(huà)的使用

? ? ? ?在A(yíng)PP開(kāi)發(fā)的過(guò)程中,在合適的時(shí)機(jī)引入合適的動(dòng)畫(huà)贤壁。會(huì)讓我們的APP動(dòng)起來(lái)苔货,更加的吸引眼球。這里我們就來(lái)總結(jié)下Android里面的動(dòng)畫(huà)敢靡。按照我們的理解Android里面動(dòng)畫(huà)分為三類(lèi):屬性動(dòng)畫(huà)(Property Animation)挂滓、視圖動(dòng)畫(huà)(View Animation)、過(guò)渡動(dòng)畫(huà)(Transition Animation)啸胧。

? ? ? ?在說(shuō)Android的這三種動(dòng)畫(huà)之前杂彭,我們先的來(lái)提一下TimeInterpolator插值器墓毒。我們可以把所有的動(dòng)畫(huà)簡(jiǎn)單的認(rèn)為是:在指定的時(shí)間內(nèi),從第一種狀態(tài)動(dòng)態(tài)的過(guò)渡到第二種狀態(tài)的一個(gè)過(guò)程亲怠。那在這個(gè)持續(xù)時(shí)間內(nèi)動(dòng)畫(huà)的執(zhí)行過(guò)程中是怎么個(gè)變化的呢所计,線(xiàn)性變換,加速變換還是其他的什么變換团秽。所以這里就會(huì)引入一個(gè)插值器的概念主胧。插值器改變的就是不同時(shí)間進(jìn)度上的值,雖然時(shí)間的流逝是線(xiàn)性的(速度是不變)习勤,但是插值器可以通過(guò)改變不同時(shí)間上對(duì)應(yīng)的動(dòng)畫(huà)的值踪栋,來(lái)達(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 線(xiàn)性插值器
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 在開(kāi)始的時(shí)候向后然后向前甩
AnticipateInterpolator
OvershootInterpolator @android:anim/overshoot_interpolator 向前甩一定值后再回到原來(lái)位置
OvershootInterpolator
AnticipateOvershootInterpolator @android:anim/anticipate_overshoot_interpolator 開(kāi)始的時(shí)候向后然后向前甩一定值后返回最后的值
AnticipateOvershootInterpolator
BounceInterpolator @android:anim/bounce_interpolator 動(dòng)畫(huà)結(jié)束的時(shí)候彈起
BounceInterpolator
CycleInterpolator @android:anim/cycle_interpolator 動(dòng)畫(huà)循環(huán)播放特定的次數(shù),速率改變沿著正弦曲線(xiàn)
CycleInterpolator

一予颤、屬性動(dòng)畫(huà)(Property Animation)

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

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

1.1驳庭、屬性動(dòng)畫(huà)源碼過(guò)程分析

? ? ? ?我們站在源代碼的角度上來(lái)看屬性動(dòng)畫(huà)刑顺。和屬性動(dòng)畫(huà)相關(guān)的類(lèi)有:AnimatorSet、ObjectAnimator饲常、ValueAnimator蹲堂、TimeInterpolator插值器、TypeEvaluator估值器贝淤。

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

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

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

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

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

  3. 定義屬性動(dòng)畫(huà):AnimatorSet宾濒、ObjectAnimator腿短、ValueAnimator。

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

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

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

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

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

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

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

    <!-- 屬性動(dòng)畫(huà)沒(méi)有指定屬性虑乖,要自己去處理回調(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)畫(huà)懦趋,具體實(shí)例:

第一步,創(chuàng)建一個(gè)XML屬性動(dòng)畫(huà)資源文件(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)畫(huà)

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

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

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

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

    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)畫(huà)(View Animation)

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

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

2.1臼予、補(bǔ)間動(dòng)畫(huà)(Tween animation)

? ? ? ?所有補(bǔ)間動(dòng)畫(huà)的基類(lèi)都是Animation鸣戴,而且所有補(bǔ)間動(dòng)畫(huà)的變換過(guò)程都是圍繞Matrix做操作,Matrix是一個(gè)3*3的變形矩陣粘拾。通過(guò)高等數(shù)學(xué)里面矩陣的運(yùn)算公式做平移窄锅,縮放,斜切缰雇,旋轉(zhuǎn)等變換入偷。簡(jiǎn)單來(lái)說(shuō)就是在動(dòng)畫(huà)過(guò)程中隨著時(shí)間的推移Matrix通過(guò)某種變換(平移,縮放械哟,斜切盯串,旋轉(zhuǎn)等)轉(zhuǎn)換到目標(biāo)Matrix。然后把Matrix的變化作用在視圖上戒良。

2.1.1体捏、補(bǔ)間動(dòng)畫(huà)源碼簡(jiǎn)單分析

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

    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)畫(huà)
        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)過(guò)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上,這樣繪制出來(lái)的視圖就是動(dòng)畫(huà)變換之后的結(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)畫(huà)變化過(guò)程中某抓,變換的結(jié)果要保存的變量
        final Transformation t = parent.getChildTransformation();
        //動(dòng)畫(huà)變化更新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)畫(huà)變換之后的結(jié)果會(huì)保存在parent.getChildTransformation()里面否副,之后在通過(guò)canvas.concat(transformToApply.getMatrix());把變換的結(jié)果運(yùn)用到視圖Cavans上來(lái)汉矿,產(chǎn)生相應(yīng)的動(dòng)畫(huà)效果。

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

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

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

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

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

  3. 監(jiān)聽(tīng)補(bǔ)間動(dòng)畫(huà)的執(zhí)行過(guò)程(AnimationListener)纽乱。

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

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

    android:duration="2000"<!-- 動(dòng)畫(huà)持續(xù)時(shí)間 -->
    android:repeatMode="restart"<!-- 動(dòng)畫(huà)重復(fù)模式 -->
    android:repeatCount="3"<!-- 動(dòng)畫(huà)播放次數(shù) -->
    android:fillAfter="true"<!-- 動(dòng)畫(huà)結(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)畫(huà)肺稀。大部分情況下這五種補(bǔ)間動(dòng)畫(huà)都能滿(mǎn)足我們的需求第股。

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

? ? ? ?改變View的位置话原。

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

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

2.1.2.2黄虱、縮放動(dòng)畫(huà)(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方向縮放的開(kāi)始值(1表示不縮放) -->
    android:fromYScale=""<!-- y方向縮放的開(kāi)始值(1表示不縮放) -->
    android:toXScale=""<!-- x方向縮放的結(jié)束值(1表示不縮放) -->
    android:toYScale=""<!-- y方向縮放的結(jié)束值(1表示不縮放) -->
    />

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

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

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

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

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

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

? ? ? ?多個(gè)動(dòng)畫(huà)組合起來(lái)對(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)畫(huà)的具體實(shí)例钠四,這里我們就沒(méi)有列出來(lái)了,在最下面DEMO里面有對(duì)應(yīng)的實(shí)例跪楞。

2.2缀去、幀動(dòng)畫(huà)(Frame animation)

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

2.2.1阎曹、幀動(dòng)畫(huà)源碼簡(jiǎn)單分析

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

2.2.1.1、AnimationDrawable播放動(dòng)畫(huà)簡(jiǎn)單分析

? ? ? ?幀動(dòng)畫(huà)的播放是通過(guò)調(diào)用AnimationDrawable類(lèi)的start()函數(shù)開(kāi)始的斟湃。

AnimationDrawable類(lèi)

    public void start() {
        ......
        if (!isRunning()) {
            // Start from 0th frame.
            //從第一幀開(kāi)始播放動(dòng)畫(huà)
            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()從字面意思也能看出來(lái)應(yīng)該是去設(shè)置對(duì)應(yīng)的幀

AnimationDrawable類(lèi)

    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類(lèi)

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

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

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

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

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

View

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

這樣就直接到Context里面的getDrawable()函數(shù)了供填,-> Resources類(lèi)的getDrawable()函數(shù)了,->Resources類(lèi) getDrawableForDensity()函數(shù)罢猪,->ResourcesImpl類(lèi)的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類(lèi)
        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 {
        ......
        //通過(guò)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);
        ......
    }

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

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

2.2.2演痒、幀動(dòng)畫(huà)的簡(jiǎn)單使用

? ? ? ?所有的動(dòng)畫(huà)都一樣,可以通過(guò)XML趋惨、JAVA兩種方式來(lái)定義動(dòng)畫(huà)對(duì)象鸟顺。

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

? ? ? ?XML設(shè)置幀動(dòng)畫(huà)的資源文件 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è)簡(jiǎn)單的XML定義幀動(dòng)畫(huà)的實(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)畫(huà)

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

? ? ? ?通過(guò)JAVA的方式來(lái)定義幀動(dòng)畫(huà)這個(gè)就簡(jiǎn)單的多了讯嫂。我們直接用一個(gè)簡(jiǎn)單的實(shí)例來(lái)說(shuō)明。

    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)畫(huà)兆沙,使用不當(dāng)容易發(fā)生OOM的情況欧芽,而且效率比較低。給大家推薦YY公司出的一個(gè)實(shí)現(xiàn)幀動(dòng)畫(huà)的開(kāi)源庫(kù)SVGA Animation SVGA Animation葛圃。提供了高性能動(dòng)畫(huà)播放體驗(yàn)千扔。同時(shí)SVGA是一種同時(shí)兼容 iOS / Android / Web 多個(gè)平臺(tái)的動(dòng)畫(huà)格式。

三装悲、過(guò)渡動(dòng)畫(huà)(Transition Animation)

? ? ? ?在A(yíng)ndroid 4.4 Transition 就已經(jīng)引入了昏鹃,但在A(yíng)ndroid 5.0(API 21)之后尚氛,Transition 被更多的應(yīng)用起來(lái)诀诊。相對(duì)于View Animation或Property Animator,Transition動(dòng)畫(huà)更加具有特殊性阅嘶,Transition可以看作對(duì)Property Animator的高度封裝属瓣。不同于A(yíng)nimator载迄,Transition動(dòng)畫(huà)具有視覺(jué)連續(xù)性的場(chǎng)景切換。

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

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

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末熊昌,一起剝皮案震驚了整個(gè)濱河市绽榛,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌婿屹,老刑警劉巖灭美,帶你破解...
    沈念sama閱讀 212,884評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異昂利,居然都是意外死亡届腐,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,755評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門(mén)蜂奸,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)犁苏,“玉大人,你說(shuō)我怎么就攤上這事扩所】耍” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 158,369評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵碌奉,是天一觀(guān)的道長(zhǎng)短曾。 經(jīng)常有香客問(wèn)我,道長(zhǎng)赐劣,這世上最難降的妖魔是什么嫉拐? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,799評(píng)論 1 285
  • 正文 為了忘掉前任,我火速辦了婚禮魁兼,結(jié)果婚禮上婉徘,老公的妹妹穿的比我還像新娘。我一直安慰自己咐汞,他們只是感情好盖呼,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,910評(píng)論 6 386
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著化撕,像睡著了一般几晤。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上植阴,一...
    開(kāi)封第一講書(shū)人閱讀 50,096評(píng)論 1 291
  • 那天蟹瘾,我揣著相機(jī)與錄音圾浅,去河邊找鬼。 笑死憾朴,一個(gè)胖子當(dāng)著我的面吹牛狸捕,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播众雷,決...
    沈念sama閱讀 39,159評(píng)論 3 411
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼灸拍,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了砾省?” 一聲冷哼從身側(cè)響起株搔,我...
    開(kāi)封第一講書(shū)人閱讀 37,917評(píng)論 0 268
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎纯蛾,沒(méi)想到半個(gè)月后纤房,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,360評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡翻诉,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,673評(píng)論 2 327
  • 正文 我和宋清朗相戀三年炮姨,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片碰煌。...
    茶點(diǎn)故事閱讀 38,814評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡舒岸,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出芦圾,到底是詐尸還是另有隱情蛾派,我是刑警寧澤,帶...
    沈念sama閱讀 34,509評(píng)論 4 334
  • 正文 年R本政府宣布个少,位于F島的核電站洪乍,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏夜焦。R本人自食惡果不足惜壳澳,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,156評(píng)論 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望茫经。 院中可真熱鬧巷波,春花似錦、人聲如沸卸伞。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,882評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)荤傲。三九已至垮耳,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間弃酌,已是汗流浹背氨菇。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,123評(píng)論 1 267
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留妓湘,地道東北人查蓉。 一個(gè)月前我還...
    沈念sama閱讀 46,641評(píng)論 2 362
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像榜贴,于是被迫代替她去往敵國(guó)和親豌研。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,728評(píng)論 2 351

推薦閱讀更多精彩內(nèi)容