Android動畫基礎詳析 | 屬性動畫基礎及ValueAnimator

  • 2019 年 10 月 11 日
  • 筆記

為什麼要引入屬性動畫

  • 逐幀動畫主要是用來實現動畫的, 而補間動畫才能實現控件的漸入漸出、移動、旋轉和縮放效果; 屬性動畫是在Android 3.0時才引入的,之前是沒有的。

既然補間動畫和逐幀動畫已經很全了,為什麼還要引入屬性動畫呢?

  • 假設:如何利用補間動畫來將一個控件的背景色在1分鐘內從綠色變為紅色? 這個效果是沒辦法僅僅通過改變控件的漸入漸出、移動、旋轉和縮放來實現的, 但卻可以通過屬性動畫完美地實現。 這就是要引入屬性動畫的第一個原因: 屬性動畫是為了彌補視圖動畫的不足而設計的, 能夠實現補間動畫無法實現的功能。
  • 補間動畫逐幀動畫統稱為視圖動畫, 從字面意思中可以看出, 兩個動畫只能對派生自View類控件實例起作用; 而屬性動畫, 從名字中可看出它作用於控件屬性。 正因為屬性動畫能夠只針對控件某一個屬性來做動畫, 所以造就了它能單獨改變控件某一個屬性的,比如顏色這就是屬性動畫能實現補間動畫無法實現的功能的最重要的原因。
  • 視圖動畫僅能對指定的View實例控件做動畫, 屬性動畫是通過改變控件某一屬性值來做動畫的。

我們準備一個button和一個TextView, 首先給TextView控件添加了單擊響應事件, 當單擊該TextView時,會彈出Toast提示; 然後, 在單擊按鈕的時候,TextView控件開始向右下角移動。 從結果中可以看出, 在移動前,單擊TextView控件是可以彈出Toast提示的; 而在移動後,單擊TextView控件則沒有響應, 相反,單擊TextView控件原來所在的區域會彈出Toast提示。 這就說明補間動畫雖然能對控件做動畫, 但是並沒有改變控件內部的屬性值。

視圖動畫與屬性動畫的區別

  • 1.操作對象
    • 視圖動畫只能操作視圖對象(各種組件、各種View、ViewGroup);
    • 屬性動畫可以操作任意對象(除了View,還可以是基本類型數據等);
      • 動畫系統本質:給定一個初始值和一個終止值, 令對象從初始值到終止值做一個平滑的變化(變化過程可以變速、勻速、不規則速度)
    1. 屬性的改變
    • 視圖動畫沒有對屬性做真正的改變,只是做齣動畫效果而已; (位移動畫後View的響應區沒有改變;縮放動畫結束後獲取View的長寬其值亦沒有改變)
    • 屬性動畫能夠做真正的屬性改變;
      • 視圖動畫實現的效果,屬性動畫都能實現;

從直觀上來看,視圖動畫與屬性動畫有如下三點不同。 (1)引入時間不同:View Animation是在API Level 1時引入的;而Property Animation是在API Level 11時引入的,即從Android 3.0才開始有與Property Animation相關的API。 (2)所在包名不同:View Animation API在android.view.animation 包中,而Property Animation API在android.animation包中。 (3)動畫類的命名不同:View Animation中的動畫類命名都是XXXXAnimation,而Property Animation中的動畫類命名都是XXXXAnimator。

動畫屬性

  • 1 時長
  • 2 時間插值器
  • 3 重複次數以及重複模式
  • 4 動畫集
  • 5 延遲
    • 屬性動畫乾的事情,就是在一段時間內讓屬性值不斷地做變化; (變化過程可以變速、勻速、不規則速度), 一系列的屬性改變即成就了一個動畫;
  • 屬性動畫相關的類, 都被定義在了android.animation包當中, 包中有一個抽象類Animator, 它包含了以上提到的五個屬性的相關方法;
  • 動畫對象都是可悲開始、可被暫停、可被監聽的;
  • Animator的子類
    • ValueAnimator 控制值的變化; 屬性動畫乾的事情,就是在一段時間內讓屬性值不斷地做變化; ValueAnimator 就是令這個屬性值不斷地做變化的驅動;

ValueAnimator

在上篇博客Android動畫基礎詳析 | 概述、逐幀動畫、視圖動畫(附諸多實際運行效果動圖)的基礎上我們新建一個property包和一個PropertyActivity:

activity_property.xml:

<?xml version="1.0" encoding="utf-8"?>  <RealativeLayout xmlns:android="http://schemas.android.com/apk/res/android"      xmlns:app="http://schemas.android.com/apk/res-auto"      xmlns:tools="http://schemas.android.com/tools"      android:layout_width="match_parent"      android:layout_height="match_parent"      tools:context=".property.PropertyActivity">        <Button          android:id="@+id/btnValueAnimator"          android:text="Go"          android:onClick="onClick"          android:layout_width = "match_parent"          android:layout_height= "wrap_content"/>    </RealativeLayout>

PropertyActivity.java:

public class PropertyActivity extends AppCompatActivity {        private static final String TAG = "PropertyActivity";        @Override      protected void onCreate(Bundle savedInstanceState) {          super.onCreate(savedInstanceState);          setContentView(R.layout.activity_property);      }        public void onClick(View view){          switch (view.getId()){              case R.id.btnValueAnimator:                  ValueAnimator valueAnimator = ValueAnimator.ofInt(0,100);//ValueAnimator的正態方法ofInt,驅動整型數值                  valueAnimator.setDuration(100);//設置時長                  //設置屬性刷新監聽器                  valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {                      @Override                      public void onAnimationUpdate(ValueAnimator animation) {                          //通過animation對象可以獲取諸多動畫相關屬性                            //獲取當前的動畫變化完成度,範圍 0.0 - 1.0,0.0表示剛開始, 1.0表示完成                          float animatedFracion = animation.getAnimatedFraction();                          //獲取當前狀態基於正態方法的始末參數間的插值,強制轉換的類型就看正態方法的數據類型;                          int animatedValue = (int)animation.getAnimatedValue();                          //打印這兩個參數,其中%.3f即保留小數點後三位                          Log.d(TAG, "onAnimationUpdate: "                                  + String.format("%.3f %d", animatedFracion,animatedValue));                      }                  });                  valueAnimator.start();//開始動畫                  break;          }      }  }

運行代碼:

第一列數據是動畫變化完成度,第二列數據是插值, 我們可以看到打印出來的值並不是線性的,??? 因為ValueAnimator默認的插值器不是勻速的;??? 下面給ValueAnimator設置插值器即可:

...  valueAnimator.setDuration(100);//設置時長                  valueAnimator.setInterpolator(new LinearInterpolator());//設置插值器                  //設置屬性刷新監聽器  ...

ValueAnimator的簡單使用案例

  • ValueAnimator.ofFloat(0f,400f,50f,300f) 構造了一個比較複雜的動畫漸變, 值從0變到400,再回到50,最後變成300, 通過getAnimatedValue()函數來獲取當前運動點的值, 在得到當前運動點的值以後, 通過layout()函數將TextView移動到指定位置即可
  • ValueAnimator只負責對指定值區間進行動畫運算; 我們需要對運算過程進行監聽,然後自己對控件執行動畫操作。
  • ofInt()與ofFloat()的唯一區別就是傳入的數值類型不一樣,ofInt()函數需要傳入Integer類型的參數,而ofFloat()函數則需要傳入Float類型的參數。 它們的參數類型都是可變長參數,所以我們可以傳入任何數量的值; 傳進去的值列表就表示動畫時的變化範圍, 比如ofInt(2,90,45)就表示從數字2變化到數字90再變化到數字45, 所以我們傳進去的數字越多, 動畫變化就越複雜。
  • 為什麼通過getAnimatedValue()函數來獲取當前valueAnimator產生的值的時候,需要轉換成Integer/Float類型?

getAnimatedValue()函數的聲明Object getAnimatedValue(); 它返回的是Object原始類型, 那我們怎麼知道要將它轉換成什麼類型呢? 注意, 如果我們在設定動畫初始值時使用的是ofFloat()函數, 則每個值的類型必定是Float類型, 我們獲取到的類型也必然是Float類型。 同樣,如果我們使用ofInt()函數設定動畫初始值, 那麼通過getAnimatedValue()函數獲取到的值 就應該轉換為Integer類型。

常用函數匯總
  • setRepeatCount(int value)函數用於設置動畫循環次數, 設置為0表示不循環, 設置為ValueAnimation.INFINITE表示無限循環。
  • cancel()函數用於取消動畫。
  • setRepeatMode(int value)函數用於設置循環模式, 當取值為ValueAnimation.RESTART時,表示正序重新開始; 當取值為ValueAnimation.REVERSE時,表示倒序重新開始。
  • 注意:重複次數為INFINITE(無限循環)的動畫, 當Activity結束的時候,必須調用cancel()函數取消動畫, 否則動畫將無限循環,從而導致View無法釋放, 進一步導致整個Activity無法釋放,最終引起內存泄漏。
監聽器
  • animator.addUpdateListener,用於監聽動畫過程中值的實時變化。 其實在ValueAnimator中,共有兩個監聽器:
  • 當動畫開始時,會通過onAnimationStart()函數返回; 在每一次重複時,都會調用一次onAnimationRepeat()函數; 在調用cancel()函數取消動畫時,會通過onAnimationCancel()函數返回; 在動畫終止時,會調用onAnimationEnd()函數通知用戶;
移除監聽器
  • removeListener(AnimatorListener listener)函數 用於在Animator中移除指定的監聽器;
  • removeAllListeners()函數 用於移除Animator中所有的監聽器。
其他函數
  • setStartDelay(long startDelay)函數非常容易理解,就是設置延時多久後動畫開始。
  • clone()函數就是複製出來一個完全一樣的新的ValueAnimator實例, 對原來的ValueAnimator是怎麼處理的, 在這個新的實例中也採用相同的處理方式;

至此,補充一個實戰: 自定義View實戰 | 彈跳的loading效果 一個主要是由ValueAnimator實現的自定義View