本節書摘來自異步社群《android開發進階:從小工到專家》一書中的第1章,第2.4節讓應用更精彩——動畫,作者 何紅輝,更多章節内容可以通路雲栖社群“異步社群”公衆号檢視
2.4 讓應用更精彩——動畫
為了使使用者的互動更為流暢、自然,動畫已經成為一款應用中不可缺少的部分。在android中,動畫的分類較多,有最早的幀動畫、補間動畫,從android 3.0之後添加了屬性動畫,而在android 5.0中又增加了vectordrawable,使得android的動畫多種多樣,能夠滿足使用者的各種需求。
動畫實際上就是在指定的時間段内持續地修改某個屬性的值,使得該值在指定取值範圍之内平滑的過渡。如圖2-22所示是一個執行時長為40毫秒、将x從0平滑過渡為40的動畫。

從圖2-22可以看出,動畫就是在某個時間點根據一定的計算方式計算出屬性的取值,并且設定給目标對象。在動畫的執行周期内持續執行這個過程,形成動畫的效果。
2.4.1 幀動畫
幀動畫也就是我們說的frame動畫。frame動畫是一系列圖檔按照一定的順序展示的過程,和放電影的機制很相似,它的原理是在一定的時間段内切換多張有細微差異的圖檔進而達到動畫的效果。
frame動畫可以被定義在xml檔案中,也可以完全編碼實作。如果被定義在xml檔案中,可以放置在/res下的anim或drawable目錄中,檔案名可以作為資源id在代碼中引用;如果完全由編碼實作,需要使用到animationdrawable對象。需要注意的是,當我們在xml檔案中定義幀動畫時,元素必須要作為根元素,它可以包含一或多個元素。android:onshot如果定義為true的話,此動畫隻會執行一次,如果為false則一直循環。元素代表一幀動畫,android:drawable指定此幀動畫所對應的圖檔資源,android:druation代表此幀持續的時間,機關為毫秒。
下面用一個簡單的示例示範一下幀動畫的使用。
下面為res/drawable中的5張類似的圖檔,圖檔的名字從ic_heart_0~ic_heart_4,每張圖檔都有些差異,将這幾張圖檔作為幀動畫時就能夠看到類似gif圖檔的進度條效果,如圖2-23所示。
我們定義一個名為heart_anim.xml的幀動畫存放在res/drawable目錄下,代碼如下:
定義好之後,我們還需要将動畫設定給某個view,例如,将該動畫設定為某個imageview的背景,代碼如下:
但是,此時動畫并不會在imageview顯示時啟動,我們還需要通過java代碼啟動該動畫。代碼如下:
這樣,幀動畫就會啟動了,imageview的背景就會在指定的時間間隔之内切換。
當然,也可以通過java代碼來建構幀動畫,示例如下:
通過xml還是java代碼來設定幀動畫完全取決于個人意願,當然,推薦的方式自然是xml。因為它将動畫的代碼從複雜的java代碼邏輯中隔離,使得動畫的定義更易于維護。
2.4.2 補間動畫
tween動畫是操作某個控件讓其展現出旋轉、漸變、移動、縮放的一種轉換過程,這稱成為補間動畫。同樣的,我們可以以xml形式定義動畫,也可以編碼實作。
如果以xml形式定義一個動畫,我們按照動畫的定義文法完成xml,并放置于/res/anim目錄下,檔案名可以作為資源id被引用;如果由編碼實作,需要使用到animation對象。
下面是一個補間動畫集合與補間動畫的格式,也就是說該集合裡面包含了多個自動化,在執行該動畫集合時,它們将一起執行:
xml檔案中必須有一個根元素,可以是、、、中的任意一個,也可以是來管理一個由前面幾個元素組成的動畫集合。
是一個動畫容器,管理多個動畫的群組,與之相對應的java對象是animationset。它有兩個屬性,android:interpolator代表一個插值器資源,可以引用系統自帶插值器資源,如表2-4所示。當然你也可以用自定義插值器資源,預設值是勻速插值器。android:shareinterpolator代表裡面的多個動畫是否要共享插值器,預設值為true,即共享插值器,如果設定為false,那麼的插值器就不再起作用,我們要在每個動畫中加入插值器。
是透明度的漸變動畫,可以實作淡入、淡出的效果,與之對應的java對象是alphaanimation。android:fromalpha屬性代表起始alpha值、浮點值,範圍在0.0和1.0之間,分别代表透明和完全不透明,android:toalpha屬性代表結尾alpha值、浮點值,範圍也在0.0和1.0之間。
是縮放動畫,可以實作動态調整控件尺寸的效果,與之對應的java對象是scaleanimation。android:fromxscale屬性代表起始的x方向上相對自身的縮放比例,浮點值,比如1.0代表自身無變化,0.5代表起始時縮小一倍,2.0代表放大一倍;android:toxscale屬性代表結尾的x方向上相對自身的縮放比例,浮點值;android:fromyscale屬性代表起始的y方向上相對自身的縮放比例,浮點值;android:toyscale屬性代表結尾的y方向上相對自身的縮放比例,浮點值;android:pivotx屬性代表縮放的中軸點x坐标,浮點值;android:pivoty屬性代表縮放的中軸點y坐标,浮點值,對于這兩個屬性,如果我們想表示中軸點為圖像的中心,可以把兩個屬性值定義成0.5或者50%。
是位移動畫,代表一個水準、垂直的位移。與之對應的java對象是translateanimation。android:fromxdelta屬性代表起始x方向的位置,android:toxdelta代表結尾x方向上的位置,android:fromyscale屬性代表起始y方向上的位置,android:toydelta屬性代表結尾y方向上的位置,以上4個屬性都支援3種表示方式:浮點數、num%、num%p。如果以浮點數字表示,代表相對自身原始位置的像素值;如果以num%表示,代表相對于自己的百分比,比如toxdelta定義為100%就表示在x方向上移動自己的1倍距離;如果以num%p表示,代表相對于父類元件的百分比。
是旋轉動畫,與之對應的java對象是rotateanimation。android:fromdegrees屬性代表起始角度,浮點值,機關:度;android:todegrees屬性代表結尾角度,浮點值,機關:度;android:pivotx屬性代表旋轉中心的x坐标值,android:pivoty屬性代表旋轉中心的y坐标值,這兩個屬性也有3種表示方式,數字方式代表相對于自身左邊緣的像素值,num%方式代表相對于自身左邊緣或頂邊緣的百分比,num%p方式代表相對于父容器的左邊緣或頂邊緣的百分比。
補間動畫隻能運用在view對象之上,并且功能相對來說較為局限。例如旋轉動畫隻能夠在x、y軸進行,而不能在z軸方向進行旋轉。是以,補間動畫通常用于執行一些比較簡單的動畫。由于比較簡單,我們在此不過多贅述。
2.4.3 屬性動畫
在android 3.0之後,android推出了新的動畫包,也就是屬性動畫。屬性動畫機制不再是針對view來設計的,也不限定于隻能實作移動、縮放、旋轉和淡入、淡出這幾種簡單的動畫操作,同時也不再隻是一種視覺上的動畫效果。它實際上是一種在一定時間段内不斷修改某個對象的某個屬性值的機制。是以我們仍然可以通過屬性動畫将一個view進行移動或者縮放,但同時也可以對view的其他屬性進行動畫操作。我們隻需要告訴系統動畫要操作的屬性、動畫時長、需要執行哪種類型的動畫,以及動畫的初始值和結束值,剩下的工作就可以全部交給系統去完成了。
2.4.3.1 屬性動畫的核心類——valueanimator
valueanimator是整個屬性動畫機制當中最核心的一個類,它的作用就是在一定的時間段内不斷地修改對象的某個屬性值。前文我們已經說過,屬性動畫的基本原理就是通過不斷地修改對象的屬性值來實作。valueanimator的内部使用一種時間循環的機制來計算值與值之間的動畫過渡,我們隻需要将屬性的取值範圍、運作時長提供給valueanimator,那麼它就會自動幫我們計算屬性值在各個動畫運作時段的取值,這些值會按照一定的計算方式來實作平滑過渡。除此之外,valueanimator還負責管理動畫的播放次數、播放模式,以及對動畫設定監聽器等,這使得它成為屬性動畫中最核心的類型。
valueanimator不僅功能強大,它的api也設計得非常簡單。通常我們都是通過offloat、ofint等靜态工廠函數建構valueanimator。例如下面是我們将數值從0.0過渡到1.0的動畫:
當然,我們也可以在res/anim目錄下的xml檔案中定義該動畫,實作如下:
啟動動畫之後,每次更新屬性值時就會調用onanimationupdate函數,在這裡可以擷取新的屬性值。當然,在這裡我們并沒有将這個值運用到具體的對象上。但它是非常靈活的實作,它隻操作屬性值本身,這個值不屬于某個具體的對象,但它卻能運用于任意對象之上。例如,通過valueanimator動畫将某個view的透明度從0.0過渡到1.0,那麼可以對onanimationupdate做如下修改:
這樣一來,我們就将valueanimator與具體的對象結合在一起,通過這種形式就能更自由地控制動畫,完成各種各樣的動畫效果。
2.4.3.2 對任意屬性進行動畫操作——object animator
valueanimator功能強大、自由度高,但是,這也意味着開發人員需要做更多的工作來實作動畫需求,這在效率緻上的軟體開發領域來說并不是一個很好的選擇。我們開發中運用更多的應該是objectanimator,因為valueanimator隻是對值進行了一個平滑的動畫過渡,但實際開發中需要做的通常是對某個對象的某個屬性值進行修改,也就是對某個對象執行動畫,當然用得最多的就是view的動畫,而objectanimator就是可以直接對任意對象的任意屬性進行動畫操作的類。
objectanimator繼承自valueanimator,是以,的動畫實作機制也與valueanimator一緻,是以,前文才說valueanimator是屬性動畫中最核心的類。objectanimator最常用的形式也是通過offloat、ofint等靜态工廠形式建構animator對象,例如下述代碼就是在2秒之内将myview的alpha屬性從1.0過渡到0.3,再從0.3過渡到0.7:
ofxxx這樣的靜态工廠函數通常至少含有4參數,例如,這裡的offloat函數,參數1就是要操作的對象,參數2是要操作該對象的哪個屬性,我們這裡要操作的是alpha屬性。剩下的參數就是可變參數,也就是說它可以是0到多個。很多情況我們都是傳遞2個值,即起始值和目标值。如果是多個數值,那麼在動畫過程中将會逐個過渡到各個值。
objectanimator極為強大,它能夠操作任意對象中的任意屬性,是以,它突破了補間動畫隻運用于view的限制,使得任意對象類型都可以使用屬性動畫。它的原理是在初始時設定目标對象、目标屬性以及要經曆的屬性值,然後通過内部的計算方式計算出在各個時間段該屬性的取值,在動畫運作期間通過目标對象屬性的setter函數更新該屬性值,如果該屬性沒有setter函數,那麼将會通過反射的形式更新目标屬性值。在運作周期内不斷地計算、更新新的屬性值,進而達到對象的屬性動畫效果。
2.4.3.3 實作豐富多彩的動畫效果——animatorset
獨立的動畫能夠實作的視覺效果畢竟是相當有限的,例如,要實作一個view在平移過程中同時在y軸方向進行旋轉,這種情況就需要使用animatorset将多個動畫組合在一起執行。animatorset類提供了一個play()方法,如果我們向這個方法中傳入一個animator對象将會傳回一個animatorset.builder的執行個體,animatorset.builder中包括以下5個核心方法,如表2-5所示。
有了這5個方法,我們就可以将各種各樣的動畫組合在一起執行,使得動畫效果更加豐富多彩。示例如下:
假設anim1~anim3是我們已經初始化好的動畫,然後我們建立一個animatorset對象,并将這3個動畫通過play、with、after進行組合,最終使得anim3首先執行,在anim3執行完成之後同時執行anim1和anim2。當然,我們也可以通過playtogether函數将3個動畫一起執行,代碼如下:
将3個動畫一起執行
通過animatorset,我們可以将多個動畫進行自由組合、排序,使得不同類型的動畫最終可以一起實作複雜的效果,滿足各種各樣的互動應用。
2.4.3.4 動畫執行時間——typeevaluator與timeinterpolator
前文多次說到,動畫的原理就是在一定時間内不斷地修改某個值。那麼在某個時間點這個屬性的值如何确定呢?
答案就是通過typeevaluator計算得到。typeevaluator的中文翻譯為類型估值器,它的作用是根據目前動畫已執行時間占總時間的百分比來計算新的屬性值。typeevaluator隻有一個evaluate函數,該函數的職責就是計算出新的屬性值。函數聲明如下:
該函數的參數1為已執行時間占總時間的百分比,取值為0.0到1.0。參數2為屬性的起始值,參數3為屬性的最終值。通常,屬性的計算公式為:
也就是已執行時間的百分比乘以兩個取值範圍的內插補點再加上起始值。例如某個動畫的總時間為1秒,動畫的功能是将view的x坐标從0移到100的位置,當已執行時間為300毫秒時,已執行時間的百分比則為30%,對應時float值為0.3,那麼此時計算得到的屬性值x則為30。它的計算公式為:
動畫運作之後可以看到輸出了如下所示的log:
fraction從0逐漸增加到1.0,在這個過程中屬性值也從0慢慢線性增加到200。線性變化也就是說屬性的變化範圍基本上比較平均,在同一個時間間隔之内屬性的變化範圍基本沒有大的變化。但是,問題是有的時候為了使動畫更動感,我們需要動畫産生一些非線性的效果,例如動畫開始前比較慢,随着時間的推移動畫越來越快,直到結束。要實作這種功能就需要timeinterpolator。
timeinterpolator中文譯為時間插值器,它的作用是修改動畫已執行時間與總時間的百分比,也就是修改fraction參數值。系統預置的有勻速的線性插值linearinterpolator、加速插值器accelerateinterpolator、減速插值器decelerateinterpolator和加速減速插值器acceleratedecelerate interpolator等。它的作用是在獲得已執行時間百分比之後,通過調用timeinterpolator的getinterpolation函數來對該百分比做出修改,并且傳回。
例如上述的加速插值器,它的實作原理是使fraction參數在動畫前面部分變化範圍小,越往後變化範圍越大。還是以1秒内從x軸的坐标0變化到200,如果使用加速動畫,在同一個時間段内得到的效果大緻如圖2-24所示。
如圖2-27所示,在300秒兩個關鍵節點的x、fraction之間的內插補點不斷遞增,0到300毫秒的x內插補點為40、fraction為0.2,300毫秒到600毫秒的x內插補點為60、fraction為0.3,依次類推,fraction變化頻率不斷增大,使得x的變化也逐漸增大,也就造成了動畫加速的效果。
那麼如何控制fraction來實作這種效果呢?
那就是timeinterpolator的任務,在動畫執行時,會調用timeinterpolator的getinterpolation函數使得開發人員有機會參與到fraction的設定,這樣開發人員就可以通過不同的timeinterpolator實作各種各樣與動畫頻率相關的效果。所謂插值器,也就是在動畫執行中“插入一腳”,影響動畫的執行。getinterpolation函數的聲明如下:
參數就是fraction本身,傳回值則是修改後的fraction值。例如線性插值器是勻速執行的,是以,它沒有修改fraction值,linearinterpolator代碼如下:
下面就來實作一個加速插值器。它的原理就是對getinterpolation的fraction參數進行乘方,因為fraction是float型,且取值在0.0~1.0,對于小數而言,是以值越小乘方之後的值就更小,當fraction慢慢變大時,乘方後的fraction值變化範圍就越來越大,也就是說通過逐漸增大統一時段内的fraction值變化範圍,即可改變動畫的執行效果。實作代碼如下:
代碼很簡單,在getinterpolation中将input參數相乘之後傳回即可。對于上述的x變化動畫,當執行時間t為100毫秒時,此時fraction為0.1,經過乘方之後得到的值為0.01,那麼在x軸坐标上移動的距離為1,此時在100毫秒内x的變化範圍是1;當t為200、fraction為0.2時,它與t為100毫秒的x內插補點為3;當t為300、fraction為0.3時,它與t為300毫秒的x內插補點為5。可見x的變化範圍按照1、3、5的規律在慢慢變大。我們看看具體的執行效果,代碼如下:
執行結果如下:
從上述程式中可以看到,正如我們分析的,fraction的變化範圍逐漸增大,x軸的變化範圍也随之增大。在同一時間段内,動畫前期x的變化較小,使得動畫看起來較慢,越往後x的內插補點越大,動畫看起來就像是加速效果。通過插值器,我們就可以很友善地在執行速率上控制動畫的執行了。