Android 一共有多少種動(dòng)畫

動(dòng)畫種類

Android 動(dòng)畫可以歸納為以下幾種:

視圖動(dòng)畫(View 動(dòng)畫)

幀動(dòng)畫(Frame 動(dòng)畫、Drawable 動(dòng)畫)

屬性動(dòng)畫

觸摸反饋動(dòng)畫(Ripple Effect)

揭露動(dòng)畫(Reveal Effect)

轉(zhuǎn)場動(dòng)畫 & 共享元素(Activity 切換動(dòng)畫)

視圖狀態(tài)動(dòng)畫(Animate View State Changes)

矢量圖動(dòng)畫(Vector 動(dòng)畫)

約束布局實(shí)現(xiàn)的關(guān)鍵幀動(dòng)畫(ConstraintSet 動(dòng)畫)

上面動(dòng)畫分類是個(gè)人通過每種動(dòng)畫種類概念的獨(dú)立性來劃分的,目前能想到的只有這么多,如果有所遺漏大家可以指點(diǎn)出來以供我后續(xù)完善。

可能有很多人迅速的反應(yīng)出,缺少了目前使用也相對(duì)較多的airbnb/lottie-android動(dòng)畫。不可置疑,Lottie 庫目前在 Android 開發(fā)中尤其復(fù)雜動(dòng)畫效果上地位顯著。但我們今天要是的 Android 原生上為我們提供的能使用的動(dòng)畫方式,Lottie 動(dòng)畫今天暫且擱置。同時(shí)對(duì)于 RecyclerView item 加載動(dòng)畫今天也暫且不提。我們且把這些動(dòng)畫歸為其他,并不是把它們遺忘了。

詳盡教程

對(duì)于上面列舉的動(dòng)畫種類,可能大家對(duì)部分較常用的動(dòng)畫早已熟練應(yīng)用,比如 View 動(dòng)畫、屬性動(dòng)畫等。而對(duì)部分較少使用(比如 揭露動(dòng)畫)、或者常常使用卻從未意識(shí)到它也屬于動(dòng)畫的一種(比如 觸摸反饋動(dòng)畫)知道的并不是那么全面。“那么今天就一一為大家詳細(xì)講解每種動(dòng)畫的概念”,那是不可能滴~~,就這么點(diǎn)篇幅,這么可能把每種動(dòng)畫都細(xì)說下來。

要這些動(dòng)畫一一梳理清晰,那將是一項(xiàng)浩大的工作,而我已經(jīng)為大家總結(jié)成了一個(gè)《詳盡 Android 動(dòng)畫系列教程》,大家可以到

https://github.com/OCNYang/Android-Animation-Set

進(jìn)行查看,由于動(dòng)畫知識(shí)點(diǎn)涉及的太多而教程詳細(xì)程度令人發(fā)指,大家可以收藏起來慢慢查看。另外總結(jié)的教程中每種動(dòng)畫都提供了動(dòng)畫示例,大家可以結(jié)合源碼細(xì)細(xì)品味。(上面總結(jié)的系列教程,大多數(shù)都是借用前人總結(jié)的教程,選用的都是針對(duì)每種動(dòng)畫網(wǎng)上流傳的最詳細(xì)全面的教程,在梳理中對(duì)部分錯(cuò)誤也進(jìn)行了更正。)

那今天的任務(wù)是什么呢?接下來主要通過粗略的介紹來講解每種動(dòng)畫在開發(fā)中都適用在哪種場景。

視圖動(dòng)畫(View 動(dòng)畫)

自從有了屬性動(dòng)畫,View 動(dòng)畫的處境就非常凄涼,但有時(shí)我們需要的僅僅就是簡易的動(dòng)畫效果,那我們使用 View 動(dòng)畫起來就十分便捷。

View 動(dòng)畫的一個(gè)特點(diǎn)就是,他的動(dòng)畫僅僅是動(dòng)的 View 的繪制地方,View 真正的位置并沒有一起動(dòng)畫。

最后,補(bǔ)間動(dòng)畫還有一個(gè)致命的缺陷,就是它只是改變了View的顯示效果而已,而不會(huì)真正去改變View的屬性。什么意思呢?比如說,現(xiàn)在屏幕的左上角有一個(gè)按鈕,然后我們通過補(bǔ)間動(dòng)畫將它移動(dòng)到了屏幕的右下角,現(xiàn)在你可以去嘗試點(diǎn)擊一下這個(gè)按鈕,點(diǎn)擊事件是絕對(duì)不會(huì)觸發(fā)的,因?yàn)閷?shí)際上這個(gè)按鈕還是停留在屏幕的左上角,只不過補(bǔ)間動(dòng)畫將這個(gè)按鈕繪制到了屏幕的右下角而已。

ObjectAnimator

相比于ValueAnimator,ObjectAnimator可能才是我們最常接觸到的類,因?yàn)閂alueAnimator只不過是對(duì)值進(jìn)行了一個(gè)平滑的動(dòng)畫過渡,但我們實(shí)際使用到這種功能的場景好像并不多。而ObjectAnimator則就不同了,它是可以直接對(duì)任意對(duì)象的任意屬性進(jìn)行動(dòng)畫操作的,比如說View的alpha屬性。

不過雖說ObjectAnimator會(huì)更加常用一些,但是它其實(shí)是繼承自ValueAnimator的,底層的動(dòng)畫實(shí)現(xiàn)機(jī)制也是基于ValueAnimator來完成的,因此ValueAnimator仍然是整個(gè)屬性動(dòng)畫當(dāng)中最核心的一個(gè)類。那么既然是繼承關(guān)系,說明ValueAnimator中可以使用的方法在ObjectAnimator中也是可以正常使用的,它們的用法也非常類似,這里如果我們想要將一個(gè)TextView在5秒中內(nèi)從常規(guī)變換成全透明,再從全透明變換成常規(guī),就可以這樣寫:

ObjectAnimator animator = ObjectAnimator.ofFloat(textview, "alpha", 1f, 0f, 1f);

animator.setDuration(5000);

animator.start();

可以看到,我們還是調(diào)用了ofFloat()方法來去創(chuàng)建一個(gè)ObjectAnimator的實(shí)例,只不過ofFloat()方法當(dāng)中接收的參數(shù)有點(diǎn)變化了。這里第一個(gè)參數(shù)要求傳入一個(gè)object對(duì)象,我們想要對(duì)哪個(gè)對(duì)象進(jìn)行動(dòng)畫操作就傳入什么,這里我傳入了一個(gè)textview。第二個(gè)參數(shù)是想要對(duì)該對(duì)象的哪個(gè)屬性進(jìn)行動(dòng)畫操作,由于我們想要改變TextView的不透明度,因此這里傳入"alpha"。后面的參數(shù)就是不固定長度了,想要完成什么樣的動(dòng)畫就傳入什么值,這里傳入的值就表示將TextView從常規(guī)變換成全透明,再從全透明變換成常規(guī)。之后調(diào)用setDuration()方法來設(shè)置動(dòng)畫的時(shí)長,然后調(diào)用start()方法啟動(dòng)動(dòng)畫,效果如下圖所示:

學(xué)會(huì)了這一個(gè)用法之后,其它的用法我們就可以舉一反三了,那比如說我們想要將TextView進(jìn)行一次360度的旋轉(zhuǎn),就可以這樣寫:

ObjectAnimator animator = ObjectAnimator.ofFloat(textview, "rotation", 0f, 360f);

animator.setDuration(5000);

animator.start();

可以看到,這里我們將第二個(gè)參數(shù)改成了"rotation",然后將動(dòng)畫的初始值和結(jié)束值分別設(shè)置成0和360,現(xiàn)在運(yùn)行一下代碼,效果如下圖所示:

那么如果想要將TextView先向左移出屏幕,然后再移動(dòng)回來,就可以這樣寫:

float curTranslationX = textview.getTranslationX();

ObjectAnimator animator = ObjectAnimator.ofFloat(textview, "translationX", curTranslationX, -500f, curTranslationX);

animator.setDuration(5000);

animator.start();

這里我們先是調(diào)用了TextView的getTranslationX()方法來獲取到當(dāng)前TextView的translationX的位置,然后ofFloat()方法的第二個(gè)參數(shù)傳入"translationX",緊接著后面三個(gè)參數(shù)用于告訴系統(tǒng)TextView應(yīng)該怎么移動(dòng),現(xiàn)在運(yùn)行一下代碼,效果如下圖所示:

然后我們還可以TextView進(jìn)行縮放操作,比如說將TextView在垂直方向上放大3倍再還原,就可以這樣寫:

ObjectAnimator animator = ObjectAnimator.ofFloat(textview, "scaleY", 1f, 3f, 1f);

animator.setDuration(5000);

animator.start();

這里將ofFloat()方法的第二個(gè)參數(shù)改成了"scaleY",表示在垂直方向上進(jìn)行縮放,現(xiàn)在重新運(yùn)行一下程序,效果如下圖所示:

到目前為止,ObjectAnimator的用法還算是相當(dāng)簡單吧,但是我相信肯定會(huì)有不少朋友現(xiàn)在心里都有同樣一個(gè)疑問,就是ofFloat()方法的第二個(gè)參數(shù)到底可以傳哪些值呢?目前我們使用過了alpha、rotation、translationX和scaleY這幾個(gè)值,分別可以完成淡入淡出、旋轉(zhuǎn)、水平移動(dòng)、垂直縮放這幾種動(dòng)畫,那么還有哪些值是可以使用的呢?其實(shí)這個(gè)問題的答案非常玄乎,就是我們可以傳入任意的值到ofFloat()方法的第二個(gè)參數(shù)當(dāng)中。任意的值?相信這很出乎大家的意料吧,但事實(shí)就是如此。因?yàn)镺bjectAnimator在設(shè)計(jì)的時(shí)候就沒有針對(duì)于View來進(jìn)行設(shè)計(jì),而是針對(duì)于任意對(duì)象的,它所負(fù)責(zé)的工作就是不斷地向某個(gè)對(duì)象中的某個(gè)屬性進(jìn)行賦值,然后對(duì)象根據(jù)屬性值的改變?cè)賮頉Q定如何展現(xiàn)出來。

那么比如說我們調(diào)用下面這樣一段代碼:

ObjectAnimator.ofFloat(textview, "alpha", 1f, 0f);

其實(shí)這段代碼的意思就是ObjectAnimator會(huì)幫我們不斷地改變textview對(duì)象中alpha屬性的值,從1f變化到0f。然后textview對(duì)象需要根據(jù)alpha屬性值的改變來不斷刷新界面的顯示,從而讓用戶可以看出淡入淡出的動(dòng)畫效果。

那么textview對(duì)象中是不是有alpha屬性這個(gè)值呢?沒有,不僅textview沒有這個(gè)屬性,連它所有的父類也是沒有這個(gè)屬性的!這就奇怪了,textview當(dāng)中并沒有alpha這個(gè)屬性,ObjectAnimator是如何進(jìn)行操作的呢?其實(shí)ObjectAnimator內(nèi)部的工作機(jī)制并不是直接對(duì)我們傳入的屬性名進(jìn)行操作的,而是會(huì)去尋找這個(gè)屬性名對(duì)應(yīng)的get和set方法,因此alpha屬性所對(duì)應(yīng)的get和set方法應(yīng)該就是:

public void setAlpha(float value);

public float getAlpha();

那么textview對(duì)象中是否有這兩個(gè)方法呢?確實(shí)有,并且這兩個(gè)方法是由View對(duì)象提供的,也就是說不僅TextView可以使用這個(gè)屬性來進(jìn)行淡入淡出動(dòng)畫操作,任何繼承自View的對(duì)象都可以的。

既然alpha是這個(gè)樣子,相信大家一定已經(jīng)明白了,前面我們所用的所有屬性都是這個(gè)工作原理,那么View當(dāng)中一定也存在著setRotation()、getRotation()、setTranslationX()、getTranslationX()、setScaleY()、getScaleY()這些方法,不信的話你可以到View當(dāng)中去找一下。

組合動(dòng)畫

獨(dú)立的動(dòng)畫能夠?qū)崿F(xiàn)的視覺效果畢竟是相當(dāng)有限的,因此將多個(gè)動(dòng)畫組合到一起播放就顯得尤為重要。幸運(yùn)的是,Android團(tuán)隊(duì)在設(shè)計(jì)屬性動(dòng)畫的時(shí)候也充分考慮到了組合動(dòng)畫的功能,因此提供了一套非常豐富的API來讓我們將多個(gè)動(dòng)畫組合到一起。

實(shí)現(xiàn)組合動(dòng)畫功能主要需要借助AnimatorSet這個(gè)類,這個(gè)類提供了一個(gè)play()方法,如果我們向這個(gè)方法中傳入一個(gè)Animator對(duì)象(ValueAnimator或ObjectAnimator)將會(huì)返回一個(gè)AnimatorSet.Builder的實(shí)例,AnimatorSet.Builder中包括以下四個(gè)方法:

after(Animator anim) ? 將現(xiàn)有動(dòng)畫插入到傳入的動(dòng)畫之后執(zhí)行

after(long delay) ? 將現(xiàn)有動(dòng)畫延遲指定毫秒后執(zhí)行

before(Animator anim) ? 將現(xiàn)有動(dòng)畫插入到傳入的動(dòng)畫之前執(zhí)行

with(Animator anim) ? 將現(xiàn)有動(dòng)畫和傳入的動(dòng)畫同時(shí)執(zhí)行

好的,有了這四個(gè)方法,我們就可以完成組合動(dòng)畫的邏輯了,那么比如說我們想要讓TextView先從屏幕外移動(dòng)進(jìn)屏幕,然后開始旋轉(zhuǎn)360度,旋轉(zhuǎn)的同時(shí)進(jìn)行淡入淡出操作,就可以這樣寫:

ObjectAnimator moveIn = ObjectAnimator.ofFloat(textview, "translationX", -500f, 0f);

ObjectAnimator rotate = ObjectAnimator.ofFloat(textview, "rotation", 0f, 360f);

ObjectAnimator fadeInOut = ObjectAnimator.ofFloat(textview, "alpha", 1f, 0f, 1f);

AnimatorSet animSet = new AnimatorSet();

animSet.play(rotate).with(fadeInOut).after(moveIn);

animSet.setDuration(5000);

animSet.start();

可以看到,這里我們先是把三個(gè)動(dòng)畫的對(duì)象全部創(chuàng)建出來,然后new出一個(gè)AnimatorSet對(duì)象之后將這三個(gè)動(dòng)畫對(duì)象進(jìn)行播放排序,讓旋轉(zhuǎn)和淡入淡出動(dòng)畫同時(shí)進(jìn)行,并把它們插入到了平移動(dòng)畫的后面,最后是設(shè)置動(dòng)畫時(shí)長以及啟動(dòng)動(dòng)畫。運(yùn)行一下上述代碼,效果如下圖所示:

Animator監(jiān)聽器

在很多時(shí)候,我們希望可以監(jiān)聽到動(dòng)畫的各種事件,比如動(dòng)畫何時(shí)開始,何時(shí)結(jié)束,然后在開始或者結(jié)束的時(shí)候去執(zhí)行一些邏輯處理。這個(gè)功能是完全可以實(shí)現(xiàn)的,Animator類當(dāng)中提供了一個(gè)addListener()方法,這個(gè)方法接收一個(gè)AnimatorListener,我們只需要去實(shí)現(xiàn)這個(gè)AnimatorListener就可以監(jiān)聽動(dòng)畫的各種事件了。

大家已經(jīng)知道,ObjectAnimator是繼承自ValueAnimator的,而ValueAnimator又是繼承自Animator的,因此不管是ValueAnimator還是ObjectAnimator都是可以使用addListener()這個(gè)方法的。另外AnimatorSet也是繼承自Animator的,因此addListener()這個(gè)方法算是個(gè)通用的方法。

添加一個(gè)監(jiān)聽器的代碼如下所示:

anim.addListener(new AnimatorListener() {

@Override

public void onAnimationStart(Animator animation) {

}

@Override

public void onAnimationRepeat(Animator animation) {

}

@Override

public void onAnimationEnd(Animator animation) {

}

@Override

public void onAnimationCancel(Animator animation) {

}

});

可以看到,我們需要實(shí)現(xiàn)接口中的四個(gè)方法,onAnimationStart()方法會(huì)在動(dòng)畫開始的時(shí)候調(diào)用,onAnimationRepeat()方法會(huì)在動(dòng)畫重復(fù)執(zhí)行的時(shí)候調(diào)用,onAnimationEnd()方法會(huì)在動(dòng)畫結(jié)束的時(shí)候調(diào)用,onAnimationCancel()方法會(huì)在動(dòng)畫被取消的時(shí)候調(diào)用。

但是也許很多時(shí)候我們并不想要監(jiān)聽那么多個(gè)事件,可能我只想要監(jiān)聽動(dòng)畫結(jié)束這一個(gè)事件,那么每次都要將四個(gè)接口全部實(shí)現(xiàn)一遍就顯得非常繁瑣。沒關(guān)系,為此Android提供了一個(gè)適配器類,叫作AnimatorListenerAdapter,使用這個(gè)類就可以解決掉實(shí)現(xiàn)接口繁瑣的問題了,如下所示:

anim.addListener(new AnimatorListenerAdapter() {

});

這里我們向addListener()方法中傳入這個(gè)適配器對(duì)象,由于AnimatorListenerAdapter中已經(jīng)將每個(gè)接口都實(shí)現(xiàn)好了,所以這里不用實(shí)現(xiàn)任何一個(gè)方法也不會(huì)報(bào)錯(cuò)。那么如果我想監(jiān)聽動(dòng)畫結(jié)束這個(gè)事件,就只需要單獨(dú)重寫這一個(gè)方法就可以了,如下所示:

anim.addListener(new AnimatorListenerAdapter() {

@Override

public void onAnimationEnd(Animator animation) {

}

});

使用XML編寫動(dòng)畫

我們可以使用代碼來編寫所有的動(dòng)畫功能,這也是最常用的一種做法。不過,過去的補(bǔ)間動(dòng)畫除了使用代碼編寫之外也是可以使用XML編寫的,因此屬性動(dòng)畫也提供了這一功能,即通過XML來完成和代碼一樣的屬性動(dòng)畫功能。

通過XML來編寫動(dòng)畫可能會(huì)比通過代碼來編寫動(dòng)畫要慢一些,但是在重用方面將會(huì)變得非常輕松,比如某個(gè)將通用的動(dòng)畫編寫到XML里面,我們就可以在各個(gè)界面當(dāng)中輕松去重用它。

如果想要使用XML來編寫動(dòng)畫,首先要在res目錄下面新建一個(gè)animator文件夾,所有屬性動(dòng)畫的XML文件都應(yīng)該存放在這個(gè)文件夾當(dāng)中。然后在XML文件中我們一共可以使用如下三種標(biāo)簽:

<animator> ?對(duì)應(yīng)代碼中的ValueAnimator

<objectAnimator> ?對(duì)應(yīng)代碼中的ObjectAnimator

<set> ?對(duì)應(yīng)代碼中的AnimatorSet

那么比如說我們想要實(shí)現(xiàn)一個(gè)從0到100平滑過渡的動(dòng)畫,在XML當(dāng)中就可以這樣寫:

<animator xmlns:android="http://schemas.android.com/apk/res/android"

? ? android:valueFrom="0"

? ? android:valueTo="100"

? ? android:valueType="intType"/>

而如果我們想將一個(gè)視圖的alpha屬性從1變成0,就可以這樣寫:

<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"

? ? android:valueFrom="1"

? ? android:valueTo="0"

? ? android:valueType="floatType"

? ? android:propertyName="alpha"/>

其實(shí)XML編寫動(dòng)畫在可讀性方面還是挺高的,上面的內(nèi)容相信不用我做解釋大家也都看得懂吧。

另外,我們也可以使用XML來完成復(fù)雜的組合動(dòng)畫操作,比如將一個(gè)視圖先從屏幕外移動(dòng)進(jìn)屏幕,然后開始旋轉(zhuǎn)360度,旋轉(zhuǎn)的同時(shí)進(jìn)行淡入淡出操作,就可以這樣寫:

<set xmlns:android="http://schemas.android.com/apk/res/android"

? ? android:ordering="sequentially" >

? ? <objectAnimator

? ? ? ? android:duration="2000"

? ? ? ? android:propertyName="translationX"

? ? ? ? android:valueFrom="-500"

? ? ? ? android:valueTo="0"

? ? ? ? android:valueType="floatType" >

? ? </objectAnimator>

? ? <set android:ordering="together" >

? ? ? ? <objectAnimator

? ? ? ? ? ? android:duration="3000"

? ? ? ? ? ? android:propertyName="rotation"

? ? ? ? ? ? android:valueFrom="0"

? ? ? ? ? ? android:valueTo="360"

? ? ? ? ? ? android:valueType="floatType" >

? ? ? ? </objectAnimator>

? ? ? ? <set android:ordering="sequentially" >

? ? ? ? ? ? <objectAnimator

? ? ? ? ? ? ? ? android:duration="1500"

? ? ? ? ? ? ? ? android:propertyName="alpha"

? ? ? ? ? ? ? ? android:valueFrom="1"

? ? ? ? ? ? ? ? android:valueTo="0"

? ? ? ? ? ? ? ? android:valueType="floatType" >

? ? ? ? ? ? </objectAnimator>

? ? ? ? ? ? <objectAnimator

? ? ? ? ? ? ? ? android:duration="1500"

? ? ? ? ? ? ? ? android:propertyName="alpha"

? ? ? ? ? ? ? ? android:valueFrom="0"

? ? ? ? ? ? ? ? android:valueTo="1"

? ? ? ? ? ? ? ? android:valueType="floatType" >

? ? ? ? ? ? </objectAnimator>

? ? ? ? </set>

? ? </set>

</set>

這段XML實(shí)現(xiàn)的效果和我們剛才通過代碼來實(shí)現(xiàn)的組合動(dòng)畫的效果是一模一樣的,每個(gè)參數(shù)的含義都非常清楚,相信大家都是一看就懂,我就不再一一解釋了。

最后XML文件是編寫好了,那么我們?nèi)绾卧诖a中把文件加載進(jìn)來并將動(dòng)畫啟動(dòng)呢?只需調(diào)用如下代碼即可:

Animator animator = AnimatorInflater.loadAnimator(context, R.animator.anim_file);

animator.setTarget(view);

animator.start();

調(diào)用AnimatorInflater的loadAnimator來將XML動(dòng)畫文件加載進(jìn)來,然后再調(diào)用setTarget()方法將這個(gè)動(dòng)畫設(shè)置到某一個(gè)對(duì)象上面,最后再調(diào)用start()方法啟動(dòng)動(dòng)畫就可以了,就是這么簡單。

好的,通過本篇文章的學(xué)習(xí),我相信大家已經(jīng)對(duì)屬性動(dòng)畫的基本用法已經(jīng)有了一個(gè)相當(dāng)不錯(cuò)的認(rèn)識(shí),并把最常用的一些功能都掌握好了,那么本篇文章的內(nèi)容就到這里,下篇文章當(dāng)中會(huì)繼續(xù)介紹屬性動(dòng)畫,講解ValueAnimator和ObjectAnimator的高級(jí)用法,感興趣的朋友請(qǐng)繼續(xù)閱讀 Android屬性動(dòng)畫完全解析(中),ValueAnimator和ObjectAnimator的高級(jí)用法?。

————————————————

版權(quán)聲明:本文為CSDN博主「guolin」的原創(chuàng)文章,遵循CC 4.0 BY-SA版權(quán)協(xié)議,轉(zhuǎn)載請(qǐng)附上原文出處鏈接及本聲明。

原文鏈接:https://blog.csdn.net/guolin_blog/article/details/43536355

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

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

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