上篇文章介紹了應用程式內對使用者操作響應的相關方法位置,簡單的響應邏輯可以是從一個介面Activity跳轉到另一個介面Activity,也可以是某些檢視View的相對變化。然而不管是啟動一個介面執行新介面Activity的生命週期方法,還是檢視的相對變化,都需要一段時間,所以在響應的最終結果完成之前是有一段空白時間的。而在這段或長或短的時間裡,該怎麼給使用者展示介面呢?這就用到Android系統推薦的動畫流程了。

廣義上說,Android系統在螢幕上繪製展示給使用者的內容發生變化時,都可以使用相關動畫過渡。與使用者操作的響應一致,根據動畫的作用物件不同,展示動畫的效果可以作用於介面Activity,也可以作用於檢視View。大多文章是基於動畫分類的幀動畫、補間動畫介紹,而這裡將按照動畫的作用物件分別展開介紹。

檢視動畫

檢視動畫可以作用於任何需要展示動畫效果的檢視View,檢視動畫是Android系統最原生的一種動畫型別,檢視動畫的定義類可以檢視android.view.animation.Animation,其子類便是檢視動畫的效果分類,包括漸變效果AlphaAnimation、旋轉效果RotateAnimation、縮放效果ScaleAnimation、移動效果TranslateAnimation、和將上述多種效果集合到一起的合集效果AnimationSet

由於檢視View既可以在佈局檔案中靜態宣告,也可以在程式碼中動態註冊宣告,所以類似的,檢視動畫的宣告也可以分為在佈局檔案中靜態宣告,和在程式碼中動態宣告兩種方式。但是檢視動畫的效果啟動使用,需要根據不同的使用者響應決定,所以只能在程式碼中動態使用。由於檢視動畫可能包含大量的效果資料,所以一般推薦檢視動畫靜態宣告+動態使用的方式。

動態宣告的檢視動畫效果,只需要在程式碼中定義上述五種動畫效果對應的動畫類,並設定相關資料引數即可。其中

漸變效果AlphaAnimation (float fromAlpha, float toAlpha)必須要設定fromAlpha引數作為漸變效果的起始透明度,toAlpha引數作為漸變效果的結束透明度。

旋轉效果RotateAnimation(float fromDegrees, float toDegrees, int pivotXType, float pivotXValue, int pivotYType, float pivotYValue)必須要設定fromDegrees引數作為旋轉效果的起始旋轉角度,和toDegrees引數作為旋轉效果的結束旋轉角度;其他引數可選,包括螢幕座標形式的後四個引數,pivotXType引數為pivotXValue引數型別,pivotXValue引數為繞x軸旋轉的角度值,同理,pivotYType引數為pivotYValue引數型別,pivotYValue引數為繞y軸旋轉的角度值。其中的引數型別包括Animation.ABSOLUTE絕對型別,其引數值表示絕對數值;Animation.RELATIVE_TO_SELF相對自身檢視型別,其引數值為相對自身檢視在動畫效果前的百分值;Animation.RELATIVE_TO_PARENT相對父檢視型別,其引數值為相對父檢視在動畫效果前的百分值。

縮放效果ScaleAnimation(float fromX, float toX, float fromY, float toY, int pivotXType, float pivotXValue, int pivotYType, float pivotYValue)也是以螢幕座標的形式記錄引數,必須要設定fromX引數作為縮放效果開始時在x軸方向的比例,toX引數作為縮放效果結束時在x軸方向的比例,同理,fromY引數作為縮放效果開始時在y軸方向的比例,toY引數作為縮放效果結束時在y軸方向的比例;其他引數可選,其含義與旋轉效果的可選引數類似。

移動效果TranslateAnimation(float fromXDelta, float toXDelta, float fromYDelta, float toYDelta)TranslateAnimation(int fromXType, float fromXValue, int toXType, float toXValue, int fromYType, float fromYValue, int toYType, float toYValue)同樣是以螢幕座標的形式記錄引數,這兩個構造方法可以任選一種,其中當八參構造方法中的Type系列引數值為Animation.RELATIVE_TO_SELF時,Value系列引數所表示的資料與四參構造方法的Delta系列引數一致。

合集效果AnimationSet(boolean shareInterpolator)可以根據shareInterpolator引數決定該動畫內的一系列動畫展示時是否使用當前類物件中定義的插值器,之後可以呼叫該物件的addAnimation(Animation a)依次放入要執行的系列檢視動畫物件。

這裡提到的插值器,是實現了android.view.animation.Interpolator介面的類,這些插值器類計算了檢視動畫效果中從開始到結束之間那段時間的效果展示,在上述檢視動畫效果Animation物件中,可以通過呼叫setInterpolator(Interpolator i)方法設定,如果不設定會預設使用android.view.animation.LinearInterpolator線性插值器。

上述五種效果類的構造方法中,還有一種(Context context, AttributeSet attrs)引數的構造方法,使用該構造方法可以通過引數二attrs傳入靜態宣告的動畫資原始檔,從而在程式碼中使用例項化物件。

靜態宣告的檢視動畫檔案,必須儲存在res/anim/資源目錄下,符合xml格式的檔案。該檔案的根標籤必須是五種檢視動畫效果之一,包括漸變效果<alpha></alpha>、旋轉效果<scale></scale>、縮放效果<rotate></rotate>、移動效果<translate></translate>、合集效果<set></set>。而其中的必選引數和可選引數也都與程式碼動態宣告中的相對應。

最終都可以在程式碼中得到上述五種檢視動畫類的例項化物件。可以呼叫setAnimationListener(Animation.AnimationListener listener)設定動畫執行的監聽,在實現的android.view.animation.Animation.AnimationListener介面例項中,可以分別實現onAnimationStart(Animation animation)動畫開始前、onAnimationRepeat(Animation animation)動畫重複時、onAnimationEnd(Animation animation)動畫結束時的回撥監聽。在啟動動畫展示的地方,呼叫startNow()方法可以立即開始;或者先呼叫setStartTime(long startTimeMillis)設定啟動的延時時間,再呼叫start()方法開始計時,等延時時間之後開始展示動畫。

圖片動畫

點陣圖動畫

在AndroidSDK提供的系統檢視中,有一種類似幕布繪製圖像的系列檢視,像android.widget.ImageView,這種具有繪製畫素點陣圖功能的檢視,更適合在其中繪製展示多張圖片連續組合的幀動畫。本質上圖片動畫只是一系列圖片的組合,所以其宣告和使用方式更隨意,不僅可以在程式碼中動態宣告+動態使用的方式,也可以直接在資原始檔中靜態宣告+靜態使用。不過因為圖片動畫需要載入大量的圖片,所以在程式碼中動態宣告使用的方式是在應用程式執行過程中執行的,可能會影響使用者的流暢度,所以推薦圖片動畫使用靜態宣告+靜態使用的方式。

動態宣告的圖片動畫可以使用android.graphics.drawable.AnimationDrawable類例項化載入點陣圖組成的幀動畫。之後依次呼叫該物件的addFrame(Drawable frame, int duration)方法增加要展示的每一幀圖片,其中引數一frame即指定了要載入的圖片資源,引數二duration則表示當前圖片資源幀在整個動畫播放中的時長,單位是毫米。也可以呼叫該物件的setOneShot(boolean oneShot)設定當前系列幀的圖片動畫是否只播放一遍。最終在需要啟動圖片動畫的位置,呼叫該物件的start(),而在需要停止圖片動畫的位置,呼叫物件的stop()

這裡需要注意的是,圖片動畫啟動的start()方法必須要在介面Activity的宣告週期方法執行完onCreate()之後呼叫。

靜態宣告的圖片動畫檔案,必須儲存在res/drawable/目錄下,符合xml格式的檔案。該檔案的根標籤必須是<animation-list></animation-list>,在該標籤中可以使用android:oneshot屬性值為truefalse,來表示當前系列幀的圖片動畫是否只播放一遍。而其中的每一幀圖片使用<item />中的android:drawable屬性值作為drawable資原始檔引用,同時要通過android:duration屬性值設定當前圖片幀的展示時長,根據人眼的識別速度,通常設定在1000(單位毫秒)以下。之後如果想靜態使用,可以在要顯示動畫的檢視中,通過設定其android:backgroud屬性,並將靜態宣告的資原始檔名作為drawable資源型別賦值,即可關聯使用。如果想動態使用,首先在程式碼中找到要顯示動畫的檢視對應的物件,呼叫該物件的setBackgroundResource(int res),同樣將靜態宣告的資原始檔名作為R.drawable資源型別賦值,也可關聯使用。最終,在需要啟動圖片動畫的程式碼中,通過呼叫檢視物件的getBackgroud()方法獲取到圖片動畫的介面android.graphics.drawable.Animatable物件,就可以在需要啟動和停止圖片動畫的位置分別呼叫start()stop()方法。

向量圖動畫

另外, 在Android5.0 即API 21及以上的版本中,通過在專案modle中增加專案依賴庫support-vector-drawableanimated-vector-drawable,以支援在資原始檔中定義繪製向量圖,在AndroidStudio建立預設專案時,所使用的預設應用icon就是向量圖物件,其優勢就是縮放仍不失真、體積小等,這裡不詳介紹。針對這種向量圖物件,也可以更加快速的增加向量圖動畫。由於向量圖只能在資原始檔中靜態宣告,相應的,向量圖動畫也只能在資原始檔中靜態宣告。這種動畫效果主要依賴於android.graphics.drawable.AnimatedVectorDrawable類或最新包向下相容的androidx.vectordrawable.graphics.drawable.AnimatedVectorDrawableCompat類。

向量圖的宣告是在res/drawable/資源目錄下,以<vector></vector>標籤所包裹的一層表示普通向量圖,可以在該標籤內部增加<group></group>標籤包裹一組動畫效果,設定其android:name屬性標記該組動畫名稱,同時使用檢視動畫相關屬性值作為檢視動畫效果展示,包括漸變、旋轉、縮放、平移。也可以在<group>標籤內部,繼續使用<path></path>標籤定義一系列路徑,同樣需要設定其android:name屬性標記該組路徑名稱。

向量動畫的宣告是在res/animator/資源目錄下,以<objectAnimator></objectAnimator>為根標籤包裹的動畫效果,通過設定其android:propertyName屬性值,可以描述檢視動畫效果中的漸變、縮放、旋轉、平移等效果,其內部屬性與靜態宣告的檢視動畫中的屬性類似。而以<set></set>為根標籤則可以包裹一系列上述四種動畫效果所表示的<objectAnimator></objectAnimator>標籤。

最後是將向量圖與向量動畫關聯使用,需要在res/drawable資源目錄下建立新的xml資原始檔,以<animated-vector></animated-vector>作為根標籤,並設定其android:drawable屬性並引用上述普通向量圖資原始檔為drawable資源型別賦值。在根標籤內通過<target android:name="" android:animation="" />分別為向量圖定義中的android:name屬性值與向量圖宣告中的資原始檔相關聯。最終在引用普通向量圖資原始檔的位置改為引用動畫向量圖資原始檔。

介面動畫

介面動畫僅在Android5.0即 API 21 及以上的版本中支援。介面動畫作用於介面Activity,只有在兩個介面Activity相互啟動切換時,才需要展示介面動畫,因此介面動畫的展示物件主要分三種,包括作為舊介面Activity在退出時動畫效果,作為新介面Activity在進入動畫效果,和兩個介面之間如果有相同內容的同類檢視,稱之為共享檢視,其在介面切換時的動畫效果。而關於這些物件的動畫效果,AndroidSDK提供了一些統一的動畫效果可供選擇,包括針對退出和進入動畫的淡入淡出效果的android.transition.Fade類、移動效果android.transition.Slide類、爆炸效果android.transition.Explode類;還有針對共享檢視的檢視動畫效果。或者也可以繼承android.transition.Transition實現自定義動畫效果。

由於介面Activity的定義是在清單檔案中靜態註冊的,所以介面動畫的使用也可以在註冊時採用靜態宣告使用的方式。在靜態使用時用到了<activity android:style/>樣式屬性,該屬性值是已經定義了<style></style>標籤的xml格式的樣式資原始檔。在<style>樣式標籤中,必須包含屬性parent="android:Theme.Material"或其子樣式名。

要想啟動介面動畫,首先在該樣式中增加一條開關控制。

<item name="android:windowActivityTransitions">true</item>

之後重寫該樣式標籤中的<item>標籤,根據name屬性值控制不同的動畫物件,而根據<item></item>標籤值指定不同的動畫效果。這裡將不同動畫物件對應關係列為下列表格。

動畫物件 name值
作為新介面進入動畫 android:windowEnterTransition
作為舊介面退出動畫 android:windowExitTransition
作為新介面進入時共享檢視動畫 android:windowSharedElementEnterTransition
作為舊介面退出時共享檢視動畫 android:windowSharedElementExitTransition

既然介面動畫是通過樣式定義的方式靜態宣告,那肯定也能在介面Activity建立後的方法回撥中通過程式碼動態宣告使用。這裡用到了android.view.Window類,因為樣式的修改要在介面載入佈局之前,所以在setContentView()方法之前,通過呼叫getWindow()方法可以得到Window物件。

首先同樣需要呼叫該物件的requestFeature(Window.FEATURE_CONTENT_TRANSITIONS)方法開啟介面動畫開關。

之後呼叫該物件的系列方法來控制不同動畫物件,根據傳入的Transition物件的例項指定不同的動畫效果。不同動畫物件的對應關係如下表。

動畫物件 Window類呼叫方法
作為新介面進入動畫 setEnterTransition()
作為舊介面退出動畫 setExitTransition()
作為新介面進入時共享檢視動畫 setSharedElementEnterTransition()
作為舊介面退出時共享檢視動畫 setSharedElementExitTransition()

針對共享檢視的動畫,還需要分別在兩個佈局檔案中標記共享檢視,使用android:transitionName屬性指定相同的共享檢視名字。

在宣告介面動畫效果後,還需要在該介面作為新介面被啟動的位置或者該介面作為舊介面啟動其他介面的位置,將原有的startActivity(Intent intent)方法修改為startActivity(Intent intent, Bundle bundle)方法。其中的intent引數還是之前要啟動介面的意圖資訊,bundle引數則是新增的控制介面動畫的資料包。可以通過ActivityOptions.makeSceneTransitionAnimation(this).toBundle()系列方法獲取當前介面宣告的介面動畫效果,其結果值作為android.os.Bundle物件傳入即可。

如果在介面動畫中有單個共享檢視動畫,可以參考ActivityOptions.makeSceneTransitionAnimation(Activity activity, View sharedElement, String sharedElementName)方法。

如果在介面動畫中有多個共享檢視動畫,可以參考ActivityOptions.makeSceneTransitionAnimation(Activity activity, Pair...<View, String> sharedElements)方法。

除了上面作用於三種物件的基本動畫型別,Android系統還提供了一種作用於任何物件的屬性動畫,該動畫具有更全面的功能,詳情可期待下篇文章。