? ? ? ?在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估值器。
ObjectAnimator、ValueAnimator:都是用于指定單個(gè)動(dòng)畫,唯一區(qū)別就是ObjectAnimator可以直接指定動(dòng)畫對(duì)象的屬性,當(dāng)然這個(gè)動(dòng)畫對(duì)象必須實(shí)現(xiàn)相應(yīng)的get,set方法。ValueAnimator的使用呢,咱們就需要自己去處理ValueAnimator.AnimatorUpdateListener監(jiān)聽每個(gè)時(shí)刻動(dòng)畫數(shù)據(jù)的變化,然后把變化的值設(shè)置給指定對(duì)象的屬性。更加詳細(xì)的內(nèi)容請參考:Android屬性動(dòng)畫ValueAnimator源碼簡單分析、Android屬性動(dòng)畫ObjectAnimator源碼簡單分析
AnimatorSet:可以定義多個(gè)(AnimatorSet、ObjectAnimator、ValueAnimator)動(dòng)畫,是動(dòng)畫的集合。更加詳細(xì)的內(nèi)容請參考:Android屬性動(dòng)畫AnimatorSet源碼簡單分析。
TimeInterpolator插值器:根據(jù)時(shí)間流速計(jì)算數(shù)值變化比例,重點(diǎn)是數(shù)值變化的比例。這個(gè)咱們在文章的一開始已經(jīng)提到了。更加詳細(xì)的內(nèi)容請參考:Android動(dòng)畫TimeInterpolator(插值器)和TypeEvaluator(估值器)分析。
TypeEvaluator估值器:根據(jù)插值器計(jì)算出的數(shù)值變化比例,計(jì)算最終的具體數(shù)值。當(dāng)要變化的值自定義對(duì)象的時(shí)候可能就需要自定義TypeEvaluator估值器了。更加詳細(xì)的內(nèi)容請參考:Android動(dòng)畫TimeInterpolator(插值器)和TypeEvaluator(估值器)分析。
? ? ? ?因?yàn)榍岸螘r(shí)間通過源碼走讀的方式已經(jīng)對(duì)屬性動(dòng)畫的過程做了一個(gè)簡單的分析,所以上面我很多地方我就直接貼了鏈接地址。如果大家在看的過程中有什么疑問可以留言,在我的能力范圍會(huì)為大家解答的。
1.2、屬性動(dòng)畫的使用
? ? ? ?屬性動(dòng)畫的使用,一般可以分為以下幾個(gè)步驟:
確定屬性動(dòng)畫作用的對(duì)象。對(duì)象可以是View也可以是其他任何對(duì)象。
確定屬性動(dòng)畫作用對(duì)象屬性,和屬性值的變化范圍。
定義屬性動(dòng)畫:AnimatorSet、ObjectAnimator、ValueAnimator。
選擇合適的插值器。(從系統(tǒng)給提供的九種插值器里面選擇或者直接自定義一個(gè)插值器)。
選擇合適的估值器。(如果我們屬性的參數(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è)步驟:
創(chuàng)建補(bǔ)間動(dòng)畫。
執(zhí)行補(bǔ)間動(dòng)畫。
監(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