前篇文章說過,Android框架還提供了兩種動畫體系,前一篇已經總結了檢視動畫(View Animation)的用法,本篇則接著總結另一種動畫體系——屬性動畫(Property Animation)的用法。
檢視動畫只能作用於View,而且檢視動畫改變的只是View的繪製效果,View真正的屬性並沒有改變。比如,一個按鈕做平移的動畫,雖然按鈕的確做了平移,但按鈕可點選的區域並沒隨著平移而改變,還是在原來的位置。而屬性動畫則可以改變真正的屬性,從而實現按鈕平移時點選區域也跟著平移。通俗點說,屬性動畫其實就是在一定時間內,按照一定規律來改變物件的屬性,從而使物件展現出動畫效果。
屬性動畫是在android 3.0引入的動畫體系,如果還想適配基本已經滅絕的2.x版本,只好繞道了。
屬性動畫和檢視動畫一樣,可以通過xml檔案定義,不同的是,檢視動畫的xml檔案放於res/anim/目錄下,而屬性動畫的xml檔案則放於res/animator/目錄下。一個是anim,一個是animator,別搞錯了。同樣的,在Java程式碼裡引用屬性動畫的xml檔案時,則用R.animator.filename,不同於檢視動畫,引用時為R.anim.filename。
屬性動畫主要有三個元素:<animator>、<objectAnimator>、<set>。
相對應的有三個類:ValueAnimator、ObjectAnimator、AnimatorSet。
ValueAnimator是基本的動畫類,處理值動畫,通過監聽某一值的變化,進行相應的操作。ObjectAnimator是ValueAnimator的子類,處理物件動畫。AnimatorSet則為動畫集,可以組合另外兩種動畫或動畫集。相應的三個標籤元素的關係也一樣。
樣式開發主要還是用xml的形式,所以這裡主要還是講標籤的用法。
<animator>
<animator>標籤與對應的ValueAnimator類提供了屬性動畫的核心功能,包括計算動畫值、動畫時間細節、是否重複等。執行屬性動畫分兩個步驟:
- 計算動畫值
- 將動畫值應用到物件和屬性上
ValuAnimiator只完成第一步,即只計算值,要實現第二步則需要在值變化的監聽器裡自行更新物件屬性。
通過<animator>標籤可以很方便的對ValuAnimiator進行設定,可設定的屬性如下:
- android:duration 動畫從開始到結束持續的時長,單位為毫秒
- android:startOffset 設定動畫執行之前的等待時長,單位為毫秒
- android:repeatCount 設定動畫重複執行的次數,預設為0,即不重複;可設為-1或infinite,表示無限重複
- android:repeatMode 設定動畫重複執行的模式,可設為以下兩個值其中之一:
- restart 動畫重複執行時從起點開始,預設為該值
- reverse 動畫會反方向執行
- android:valueFrom 動畫開始的值,可以為int值、float值或color值
- android:valueTo 動畫結束的值,可以為int值、float值或color值
- android:valueType 動畫值型別,若為color值,則無需設定該屬性
- intType 指定動畫值,即以上兩個value屬性的值為整型
- floatType 指定動畫值,即以上兩個value屬性的值為浮點型,預設值
- android:interpolator 設定動畫速率的變化,比如加速、減速、勻速等,需要指定Interpolator資源。具體用法在View Animation篇已經講過,這裡不再重複
接著,用一個例項講解具體的用法吧。在這個例子裡,將一個按鈕的寬度進行縮放,從100%縮放到20%。
xml檔案的程式碼如下:
1 2 3 4 5 6 7 |
<!-- res/animator/value_animator.xml --> <?xml version="1.0" encoding="utf-8"?> <animator xmlns:android="http://schemas.android.com/apk/res/android" android:duration="3000" android:valueFrom="100" android:valueTo="20" android:valueType="intType" /> |
可看到,值的變化從100到20,動畫時長3000毫秒,以下則是目標按鈕的xml程式碼:
1 2 3 4 5 6 7 |
<Button android:layout_width="match_parent" android:layout_height="wrap_content" android:background="@drawable/bg_btn_normal" android:onClick="onScaleWidth" android:text="點我" android:textColor="@android:color/white" /> |
按鈕預設是填充螢幕寬度的,點選時的執行方法為onScaleWidth,以下則是onScaleWidth方法的程式碼:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
public void onScaleWidth(final View view) { // 獲取螢幕寬度 final int maxWidth = getWindowManager().getDefaultDisplay().getWidth(); ValueAnimator valueAnimator = (ValueAnimator) AnimatorInflater.loadAnimator(this, R.animator.value_animator); valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animator) { // 當前動畫值,即為當前寬度比例值 int currentValue = (Integer) animator.getAnimatedValue(); // 根據比例更改目標view的寬度 view.getLayoutParams().width = maxWidth * currentValue / 100; view.requestLayout(); } }); valueAnimator.start(); } |
從View Animation篇中已經知道,檢視動畫是通過AnimationUtils類的loadAnimation()方法獲取xml檔案相對應的Animation類例項,而屬性動畫則是通過AnimatorInflater類的loadAnimation()方法獲取相應的Animator類例項。
另外,ValueAnimator通過新增AnimatorUpdateListener監聽器監聽值的變化,從而再手動更新目標物件的屬性。
最後,通過呼叫valueAnimator.start()方法啟動動畫。
<objectAnimator>
<objectAnimator>標籤對應的類為ObjectAnimator,為ValueAnimator的子類。<objectAnimator>標籤與<animator>標籤不同的是,<objectAnimator>可以直接指定動畫的目標物件的屬性。標籤可設定的屬性除了和<animator>一樣的那些,另外多了一個:
- android:propertyName 目標物件的屬性名,要求目標物件必須提供該屬性的setter方法,如果動畫的時候沒有初始值,還需要提供getter方法
還是用例項說明具體用法,還是用上面的例子,將一個按鈕的寬度進行縮放,從100%縮放到20%,但這次改用<objectAnimator>實現。
以下為xml檔案的程式碼:
1 2 3 4 5 6 7 8 |
<!-- res/animator/object_animator.xml --> <?xml version="1.0" encoding="utf-8"?> <objectAnimator xmlns:android="http://schemas.android.com/apk/res/android" android:duration="3000" android:propertyName="width" android:valueFrom="100" android:valueTo="20" android:valueType="intType" /> |
與<animator>的例子相比,就只是多了一個android:propertyName的屬性,設定值為width。也就是說,動畫改變的屬性為width,值將從100逐漸減到20。另外,值是從setWidth()傳遞過去的,再從getWidth()獲取。而且,這裡設定的值代表的是比例值,因此,還需要進行計算轉化為實際的寬度值。最後,物件實際的寬度值為view.getLayoutParams().width。因此,我將用一個包裝類來包裝原始的view物件,對其提供setWidth()和getWidth()方法,程式碼如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
private static class ViewWrapper { private View target; //目標物件 private int maxWidth; //最長寬度值 public ViewWrapper(View target, int maxWidth) { this.target = target; this.maxWidth = maxWidth; } public int getWidth() { return target.getLayoutParams().width; } public void setWidth(int widthValue) { //widthValue的值從100到20變化 target.getLayoutParams().width = maxWidth * widthValue / 100; target.requestLayout(); } } |
上面setWidth()的程式碼裡,根據比例值轉化為了實際的寬度值。最後,動畫處理的程式碼如下:
1 2 3 4 5 6 7 8 9 10 11 12 |
public void onScaleWidth(View view) { // 獲取螢幕寬度 int maxWidth = getWindowManager().getDefaultDisplay().getWidth(); // 將目標view進行包裝 ViewWrapper wrapper = new ViewWrapper(view, maxWidth); // 將xml轉化為ObjectAnimator物件 ObjectAnimator objectAnimator = (ObjectAnimator) AnimatorInflater.loadAnimator(this, R.animator.object_animator); // 設定動畫的目標物件為包裝後的view objectAnimator.setTarget(wrapper); // 啟動動畫 objectAnimator.start(); } |
ObjectAnimator提供了屬性的設定,但相應的需要有該屬性的setter和getter方法。而ValueAnimator則只是定義了值的變化,並不指定目標屬性,所以也不需要提供setter和getter方法,但只能在AnimatorUpdateListener監聽器裡手動更新屬性。不過,也因為沒有指定屬性,所以其實更具靈活性了,你可以在監聽器里根據值的變化做任何事情,比如更新多個屬性,比如在縮放寬度的同時做垂直移動。
為了對View更方便的設定屬性動畫,Android系統也提供了View的一些屬性和相應的setter和getter方法:
- alpha:透明度,預設為1,表示不透明,0表示完全透明
- pivotX 和 pivotY:旋轉的軸點和縮放的基準點,預設是View的中心點
- scaleX 和 scaleY:基於pivotX和pivotY的縮放,1表示無縮放,小於1表示收縮,大於1則放大
- rotation、rotationX 和 rotationY:基於軸點(pivotX和pivotY)的旋轉,rotation為平面的旋轉,rotationX和rotationY為立體的旋轉
- translationX 和 translationY:View的螢幕位置座標變化量,以layout容器的左上角為座標原點
- x 和 y:View在父容器內的最終位置,是左上角座標和偏移量(translationX,translationY)的和
<set>
<set>標籤對應於AnimatorSet類,可以將多個動畫組合成一個動畫集,如上面提到的在縮放寬度的同時做垂直移動,可以將一個縮放寬度的動畫和一個垂直移動的動畫組合在一起。
<set>標籤有一個屬性可以設定動畫的時序關係:
- android:ordering 設定動畫的時序關係,取值可為以下兩個值之一:
- together 動畫同時執行,預設值
- sequentially 動畫按順序執行
那如果想有些動畫同時執行,有些按順序執行,該怎麼辦呢?因為<set>標籤是可以巢狀其他<set>標籤的,也就是說可以將同時執行的組合在一個<set>標籤,再嵌在按順序執行的<set>標籤內。
看例項程式碼吧,以下為xml檔案:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
<!-- res/animator/animator_set.xml --> <?xml version="1.0" encoding="utf-8"?> <set xmlns:android="http://schemas.android.com/apk/res/android" android:ordering="together"> <objectAnimator android:duration="3000" android:propertyName="width" android:valueFrom="100" android:valueTo="20" android:valueType="intType" /> <objectAnimator android:duration="3000" android:propertyName="marginTop" android:valueFrom="0" android:valueTo="100" android:valueType="intType" /> </set> |
以上程式碼可實現兩個同時執行的動畫,一個將width從100縮放到20,一個將marginTop從0增加到100。多了一個marginTop屬性,那麼,在ViewWrapper新增setMarginTop()方法,新增後的ViewWrapper類程式碼如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
private static class ViewWrapper { private View target; private int maxWidth; public ViewWrapper(View target, int maxWidth) { this.target = target; this.maxWidth = maxWidth; } public int getWidth() { return target.getLayoutParams().width; } public void setWidth(int widthValue) { target.getLayoutParams().width = maxWidth * widthValue / 100; target.requestLayout(); } public void setMarginTop(int margin) { LinearLayout.LayoutParams layoutParams = (LinearLayout.LayoutParams) target.getLayoutParams(); layoutParams.setMargins(0, margin, 0, 0); target.setLayoutParams(layoutParams); } } |
最後,動畫處理的程式碼:
1 2 3 4 5 6 7 8 9 10 11 12 |
public void onScaleWidth(View view) { // 獲取螢幕寬度 int maxWidth = getWindowManager().getDefaultDisplay().getWidth(); // 將目標view進行包裝 ViewWrapper wrapper = new ViewWrapper(view, maxWidth); // 將xml轉化為ObjectAnimator物件 AnimatorSet animatorSet = (AnimatorSet) AnimatorInflater.loadAnimator(this, R.animator.animator_set); // 設定動畫的目標物件為包裝後的view animatorSet.setTarget(wrapper); // 啟動動畫 animatorSet.start(); } |
這樣就搞定了,實現了寬度縮放和垂直移動的效果。
寫在最後
至此,檢視動畫和屬性動畫基本的用法都總結完了。示例程式碼可從github上檢視,github地址:
https://github.com/keeganlee/kstyle.git