動(dòng)畫(huà)簡(jiǎn)介
您可以通過(guò)動(dòng)畫(huà)添加視覺(jué)提示,向用戶(hù)通知應(yīng)用中的動(dòng)態(tài)。當(dāng)界面狀態(tài)發(fā)生改變時(shí)(例如有新內(nèi)容加載或有新操作可用時(shí)),動(dòng)畫(huà)尤其有用。動(dòng)畫(huà)還為應(yīng)用增加了優(yōu)美的外觀,使其擁有更高品質(zhì)的外觀和風(fēng)格。
Android 根據(jù)您需要的動(dòng)畫(huà)類(lèi)型提供不同的動(dòng)畫(huà) API,因此本頁(yè)概括介紹了向界面添加動(dòng)作的不同方法。
屬性動(dòng)畫(huà)概述
屬性動(dòng)畫(huà)系統(tǒng)是一個(gè)強(qiáng)健的框架,用于為幾乎任何內(nèi)容添加動(dòng)畫(huà)效果。您可以定義一個(gè)隨時(shí)間更改任何對(duì)象屬性的動(dòng)畫(huà),無(wú)論其是否繪制到屏幕上。屬性動(dòng)畫(huà)會(huì)在指定時(shí)長(zhǎng)內(nèi)更改屬性(對(duì)象中的字段)的值。要添加動(dòng)畫(huà)效果,請(qǐng)指定要添加動(dòng)畫(huà)效果的對(duì)象屬性,例如對(duì)象在屏幕上的位置、動(dòng)畫(huà)效果持續(xù)多長(zhǎng)時(shí)間以及要在哪些值之間添加動(dòng)畫(huà)效果。
借助屬性動(dòng)畫(huà)系統(tǒng),您可以定義動(dòng)畫(huà)的以下特性:
- 時(shí)長(zhǎng):您可以指定動(dòng)畫(huà)的時(shí)長(zhǎng)。默認(rèn)時(shí)長(zhǎng)為 300 毫秒。
- 時(shí)間插值:您可以指定如何根據(jù)動(dòng)畫(huà)的當(dāng)前已播放時(shí)長(zhǎng)來(lái)計(jì)算屬性的值。
- 重復(fù)計(jì)數(shù)和行為:您可以指定是否在某個(gè)時(shí)長(zhǎng)結(jié)束后重復(fù)播放動(dòng)畫(huà)以及重復(fù)播放動(dòng)畫(huà)多少次。您還可以指定是否要反向播放動(dòng)畫(huà)。如果將其設(shè)置為反向播放,則會(huì)先播放動(dòng)畫(huà),然后反向播放動(dòng)畫(huà),直到達(dá)到重復(fù)次數(shù)。
- Animator 集:您可以將動(dòng)畫(huà)分成多個(gè)邏輯集,它們可以一起播放、按順序播放或者在指定的延遲時(shí)間后播放。
- 幀刷新延遲:您可以指定動(dòng)畫(huà)幀的刷新頻率。默認(rèn)設(shè)置為每 10 毫秒刷新一次,但應(yīng)用刷新幀的速度最終取決于整個(gè)系統(tǒng)的繁忙程度以及系統(tǒng)為底層計(jì)時(shí)器提供服務(wù)的速度。
屬性動(dòng)畫(huà)的工作原理
首先,讓我們通過(guò)一個(gè)簡(jiǎn)單的示例來(lái)了解動(dòng)畫(huà)的工作原理。圖 1 描繪了一個(gè)假設(shè)的對(duì)象,該對(duì)象的 x 屬性(表示其在屏幕上的水平位置)添加了動(dòng)畫(huà)效果。動(dòng)畫(huà)時(shí)長(zhǎng)設(shè)置為 40 毫秒,要移動(dòng)的距離為 40 像素。該對(duì)象每隔 10 毫秒(這是默認(rèn)的幀刷新頻率)會(huì)水平移動(dòng) 10 像素。在 40 毫秒時(shí),動(dòng)畫(huà)停止,同時(shí)對(duì)象在水平位置 40 處停止。這是使用線(xiàn)性插值(表示對(duì)象以恒定速度移動(dòng))的動(dòng)畫(huà)示例。

也可以指定動(dòng)畫(huà)使用非線(xiàn)性插值。圖 2 展示了一個(gè)假設(shè)的對(duì)象,它在動(dòng)畫(huà)開(kāi)始時(shí)加速,在動(dòng)畫(huà)結(jié)束前減速。該對(duì)象仍在 40 毫秒內(nèi)移動(dòng)了 40 像素,但這種移動(dòng)是非線(xiàn)性的。開(kāi)始時(shí),此動(dòng)畫(huà)加速移動(dòng)到中間點(diǎn),然后從中間點(diǎn)減速移動(dòng),直至動(dòng)畫(huà)結(jié)束。如圖 2 所示,動(dòng)畫(huà)在開(kāi)頭和結(jié)尾移動(dòng)的距離小于在中間移動(dòng)的距離。

我們來(lái)詳細(xì)了解一下屬性動(dòng)畫(huà)系統(tǒng)的重要組成部分將如何計(jì)算如上所示的動(dòng)畫(huà)。圖 3 描繪了主類(lèi)之間是如何相互協(xié)作的。

ValueAnimator對(duì)象跟蹤動(dòng)畫(huà)的時(shí)間,例如動(dòng)畫(huà)的已運(yùn)行時(shí)長(zhǎng)以及正在添加動(dòng)畫(huà)效果的屬性的當(dāng)前值。
ValueAnimator 包含 TimeInterpolator和 TypeEvaluator;前者用于定義動(dòng)畫(huà)插值,后者用于定義如何計(jì)算正在添加動(dòng)畫(huà)效果的屬性的值。例如,在圖 2 中,所用的 TimeInterpolator為 AccelerateDecelerateInterpolator,所用的 TypeEvaluator 為 IntEvaluator。
要開(kāi)始動(dòng)畫(huà),請(qǐng)創(chuàng)建一個(gè) ValueAnimator,并為您想要添加動(dòng)畫(huà)效果的屬性賦予起始值和結(jié)束值,以及動(dòng)畫(huà)時(shí)長(zhǎng)。當(dāng)您調(diào)用 start() 時(shí),動(dòng)畫(huà)即會(huì)開(kāi)始播放。在整個(gè)動(dòng)畫(huà)播放期間,ValueAnimator 將基于動(dòng)畫(huà)時(shí)長(zhǎng)和已播放時(shí)長(zhǎng)計(jì)算已完成動(dòng)畫(huà)分?jǐn)?shù)(在 0 和 1 之間)。已完成動(dòng)畫(huà)分?jǐn)?shù)表示動(dòng)畫(huà)已完成時(shí)間的百分比,0 表示 0%,1 表示 100%。以圖 1 為例,在 t = 10ms 處,已完成動(dòng)畫(huà)分?jǐn)?shù)將為 0.25,因?yàn)榭倳r(shí)長(zhǎng) t = 40ms。
在 ValueAnimator 計(jì)算完已完成動(dòng)畫(huà)分?jǐn)?shù)后,它會(huì)調(diào)用當(dāng)前設(shè)置的TimeInterpolator 來(lái)計(jì)算插值分?jǐn)?shù)。插值分?jǐn)?shù)會(huì)將已完成動(dòng)畫(huà)分?jǐn)?shù)映射為一個(gè)新分?jǐn)?shù),該分?jǐn)?shù)會(huì)考慮已設(shè)置的時(shí)間插值。例如,在圖 2 中,由于動(dòng)畫(huà)緩慢加速,t = 10ms 時(shí)的插值分?jǐn)?shù)(約 0.15)小于已完成動(dòng)畫(huà)分?jǐn)?shù) (0.25)。在圖 1 中,插值分?jǐn)?shù)始終等于已完成動(dòng)畫(huà)分?jǐn)?shù)。
計(jì)算插值分?jǐn)?shù)后,ValueAnimator 會(huì)調(diào)用相應(yīng)的 TypeEvaluator,以根據(jù)動(dòng)畫(huà)的插值分?jǐn)?shù)、起始值和結(jié)束值來(lái)計(jì)算要添加動(dòng)畫(huà)效果的屬性的值。例如,在圖 2 中,t = 10ms 時(shí)的插值分?jǐn)?shù)為 0.15,因此,此時(shí)屬性的值為 0.15 × (40 - 0),即 6。
屬性動(dòng)畫(huà)與試圖動(dòng)畫(huà)的區(qū)別
視圖動(dòng)畫(huà)系統(tǒng)僅提供為View對(duì)象添加動(dòng)畫(huà)效果的功能,因此,如果您想為非 對(duì)象添加動(dòng)畫(huà)效果,則必須實(shí)現(xiàn)自己的代碼才能做到。視圖動(dòng)畫(huà)系統(tǒng)也存在一些限制,因?yàn)樗鼉H公開(kāi) 對(duì)象的部分方面來(lái)供您添加動(dòng)畫(huà)效果;例如,您可以對(duì)視圖的縮放和旋轉(zhuǎn)添加動(dòng)畫(huà)效果,但無(wú)法對(duì)背景顏色這樣做。
視圖動(dòng)畫(huà)系統(tǒng)的另一個(gè)缺點(diǎn)是它只會(huì)在繪制視圖的位置進(jìn)行修改,而不會(huì)修改實(shí)際的視圖本身。例如,如果您為某個(gè)按鈕添加了動(dòng)畫(huà)效果,使其可以在屏幕上移動(dòng),該按鈕會(huì)正確繪制,但能夠點(diǎn)擊按鈕的實(shí)際位置并不會(huì)更改,因此您必須通過(guò)實(shí)現(xiàn)自己的邏輯來(lái)處理此事件。
有了屬性動(dòng)畫(huà)系統(tǒng),您就可以完全擺脫這些束縛,還可以為任何對(duì)象(視圖和非視圖)的任何屬性添加動(dòng)畫(huà)效果,并且實(shí)際修改的是對(duì)象本身。屬性動(dòng)畫(huà)系統(tǒng)在執(zhí)行動(dòng)畫(huà)方面也更為強(qiáng)健。概括地講,您可以為要添加動(dòng)畫(huà)效果的屬性(例如顏色、位置或大?。┓峙?Animator,還可以定義動(dòng)畫(huà)的各個(gè)方面,例如多個(gè) Animator 的插值和同步。
不過(guò),視圖動(dòng)畫(huà)系統(tǒng)的設(shè)置需要的時(shí)間較短,需要編寫(xiě)的代碼也較少。如果視圖動(dòng)畫(huà)可以完成您需要執(zhí)行的所有操作,或者現(xiàn)有代碼已按照您需要的方式運(yùn)行,則無(wú)需使用屬性動(dòng)畫(huà)系統(tǒng)。在某些用例中,也可以針對(duì)不同的情況同時(shí)使用這兩種動(dòng)畫(huà)系統(tǒng)。
API 概述
| 類(lèi) | 說(shuō)明 |
|---|---|
| ValueAnimator | 屬性動(dòng)畫(huà)的主計(jì)時(shí)引擎,它也可計(jì)算要添加動(dòng)畫(huà)效果的屬性的值。它具有計(jì)算動(dòng)畫(huà)值所需的所有核心功能,同時(shí)包含每個(gè)動(dòng)畫(huà)的計(jì)時(shí)詳情、有關(guān)動(dòng)畫(huà)是否重復(fù)播放的信息、用于接收更新事件的監(jiān)聽(tīng)器以及設(shè)置待評(píng)估自定義類(lèi)型的功能。為屬性添加動(dòng)畫(huà)效果分為兩個(gè)步驟:計(jì)算添加動(dòng)畫(huà)效果之后的值,以及對(duì)要添加動(dòng)畫(huà)效果的對(duì)象和屬性設(shè)置這些值。ValueAnimator 不會(huì)執(zhí)行第二個(gè)步驟,因此,您必須監(jiān)聽(tīng)由 ValueAnimator 計(jì)算的值的更新情況,并使用您自己的邏輯修改要添加動(dòng)畫(huà)效果的對(duì)象。如需了解詳情,請(qǐng)參閱使用 ValueAnimator 添加動(dòng)畫(huà)效果部分。 |
| ObjectAnimator | ValueAnimator的子類(lèi),用于設(shè)置目標(biāo)對(duì)象和對(duì)象屬性以添加動(dòng)畫(huà)效果。此類(lèi)會(huì)在計(jì)算出動(dòng)畫(huà)的新值后相應(yīng)地更新屬性。在大多數(shù)情況下,您不妨使用 ObjectAnimator,因?yàn)樗梢詷O大地簡(jiǎn)化對(duì)目標(biāo)對(duì)象的值添加動(dòng)畫(huà)效果這一過(guò)程。不過(guò),有時(shí)您需要直接使用 ValueAnimator,因?yàn)?ObjectAnimator存在其他一些限制,例如要求目標(biāo)對(duì)象具有特定的訪(fǎng)問(wèn)器方法。 |
| AnimatorSet | 此類(lèi)提供一種將動(dòng)畫(huà)分組在一起的機(jī)制,以使它們彼此相對(duì)運(yùn)行。您可以將動(dòng)畫(huà)設(shè)置為一起播放、按順序播放或者在指定的延遲時(shí)間后播放。如需了解詳情,請(qǐng)參閱使用 AnimatorSet 編排多個(gè)動(dòng)畫(huà)部分。 |
評(píng)估程序負(fù)責(zé)告知屬性動(dòng)畫(huà)系統(tǒng)如何計(jì)算指定屬性的值。它們使用由 Animator 類(lèi)提供的計(jì)時(shí)數(shù)據(jù)(即動(dòng)畫(huà)的起始值和結(jié)束值),并根據(jù)這些數(shù)據(jù)計(jì)算屬性添加動(dòng)畫(huà)效果之后的值。屬性動(dòng)畫(huà)系統(tǒng)可提供以下評(píng)估程序:
| 類(lèi)/接口 | 說(shuō)明 |
|---|---|
| IntEvaluator | 這是用于計(jì)算 int 屬性的值的默認(rèn)評(píng)估程序。 |
| FloatEvaluator | 這是用于計(jì)算 float 屬性的值的默認(rèn)評(píng)估程序。 |
| ArgbEvaluator | 這是用于計(jì)算顏色屬性的值(用十六進(jìn)制值表示)的默認(rèn)評(píng)估程序。 |
| TypeEvaluator | 此接口用于創(chuàng)建您自己的評(píng)估程序。如果您要添加動(dòng)畫(huà)效果的對(duì)象屬性不是 int、float 或顏色,那么您必須實(shí)現(xiàn) TypeEvaluator 接口,才能指定如何計(jì)算對(duì)象屬性添加動(dòng)畫(huà)效果之后的值。如果您想以不同于默認(rèn)行為的方式處理 int、float和顏色,您還可以為這些類(lèi)型的值指定自定義 TypeEvaluator。如需詳細(xì)了解如何編寫(xiě)自定義評(píng)估程序,請(qǐng)參閱使用 TypeEvaluator 部分。 |
時(shí)間插值器指定了如何根據(jù)時(shí)間計(jì)算動(dòng)畫(huà)中的特定值。例如,您可以指定動(dòng)畫(huà)在整個(gè)動(dòng)畫(huà)中以線(xiàn)性方式播放,即動(dòng)畫(huà)在整個(gè)播放期間勻速移動(dòng);也可以指定動(dòng)畫(huà)使用非線(xiàn)性時(shí)間,例如動(dòng)畫(huà)在開(kāi)始后加速并在結(jié)束前減速。表 3 介紹了 android.view.animation 中包含的插值器。如果下表提供的插值器都不能滿(mǎn)足您的需求,請(qǐng)實(shí)現(xiàn) TimeInterpolator 接口并創(chuàng)建您自己的插值器。如需詳細(xì)了解如何編寫(xiě)自定義插值器,請(qǐng)參閱 使用插值器 。
| 類(lèi)/接口 | 說(shuō)明 |
|---|---|
| AccelerateDecelerateInterpolator | 該插值器的變化率在開(kāi)始和結(jié)束時(shí)緩慢但在中間會(huì)加快。 |
| AccelerateInterpolator | 該插值器的變化率在開(kāi)始時(shí)較為緩慢,然后會(huì)加快。 |
| AnticipateInterpolator | 該插值器先反向變化,然后再急速正向變化。 |
| AnticipateOvershootInterpolator | 該插值器先反向變化,再急速正向變化,然后超過(guò)定位值,最后返回到最終值。 |
| BounceInterpolator | 該插值器的變化會(huì)跳過(guò)結(jié)尾處。 |
| CycleInterpolator | 該插值器的動(dòng)畫(huà)會(huì)在指定數(shù)量的周期內(nèi)重復(fù)。 |
| DecelerateInterpolator | 該插值器的變化率開(kāi)始很快,然后減速。 |
| LinearInterpolator | 該插值器的變化率恒定不變。 |
| OvershootInterpolator | 該插值器會(huì)急速正向變化,再超出最終值,然后返回。 |
| TimeInterpolator | 該接口用于實(shí)現(xiàn)您自己的插值器。 |
使用AnimatorSet 編排多個(gè)動(dòng)畫(huà)
在許多情況下,您需要根據(jù)一個(gè)動(dòng)畫(huà)開(kāi)始或結(jié)束的時(shí)間來(lái)播放另一個(gè)動(dòng)畫(huà)。借助 Android 系統(tǒng),您可以將動(dòng)畫(huà)捆綁到一個(gè) AnimatorSet 中,以便指定是同時(shí)播放動(dòng)畫(huà)、按順序播放還是在指定的延遲時(shí)間后播放。您還可以相互嵌套 AnimatorSet 對(duì)象。
以下代碼段通過(guò)以下方式播放相應(yīng)的 Animator 對(duì)象:
- 播放 bounceAnim。
- 同時(shí)播放 squashAnim1、squashAnim2、stretchAnim1 和 stretchAnim2。
- 播放 bounceBackAnim。
- 播放 fadeAnim。
AnimatorSet bouncer = new AnimatorSet();
bouncer.play(bounceAnim).before(squashAnim1);
bouncer.play(squashAnim1).with(squashAnim2);
bouncer.play(squashAnim1).with(stretchAnim1);
bouncer.play(squashAnim1).with(stretchAnim2);
bouncer.play(bounceBackAnim).after(stretchAnim2);
ValueAnimator fadeAnim = ObjectAnimator.ofFloat(newBall, "alpha", 1f, 0f);
fadeAnim.setDuration(250);
AnimatorSet animatorSet = new AnimatorSet();
animatorSet.play(bouncer).before(fadeAnim);
animatorSet.start();