Android系統編程入門系列之介面Activity響應絲滑的傳統動畫

上篇文章介紹了應用程式內對用戶操作響應的相關方法位置,簡單的響應邏輯可以是從一個介面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系統還提供了一種作用於任何對象的屬性動畫,該動畫具有更全面的功能,詳情可期待下篇文章。