1. 程式人生 > 實用技巧 >【轉載】 android 屬性動畫詳解

【轉載】 android 屬性動畫詳解

動畫綜述

Google大大對動畫的總述如下:

Animations can add visual cues that notify users about what's going on in your app. They are especially useful when the UI changes state, such as when new content loads or new actions become available. Animations also add a polished look to your app, which gives it a higher quality look and feel.

沒錯,放上原文我只是裝個逼,~
簡單來說,動畫就兩個作用:

  • 新增可視提示,通知我們這個APP中正在發生的事情。比如使用者介面發生變化時,有新的內容載入或某些操作變為可用。
  • 提供高逼格的外觀(裝逼利器

動畫的分類如下:

動畫分類

屬性動畫(Property Animation)

概述

  • 檢視動畫的缺陷:
    • 物件的侷限性:僅限於View
    • 只改變了View的視覺效果,而沒有改變View的屬性
    • 動畫效果單一
  • 屬性動畫的特點:
    • 作用物件:任意物件,甚至沒物件也可以
    • 作用方式:改變物件的屬性
    • 動畫效果:按需自定義,不再侷限於上述4種基本變換
  • 繼承關係


    繼承關係
Java類名XML關鍵字說明
ValueAnimator <animator> 放置在res/animator/目錄下 在一個特定的時間裡執行一個動畫
TimeAnimator 時序監聽回撥工具
ObjectAnimator <objectAnimator> 放置在res/animator/目錄下 一個物件的一個屬性動畫
AnimatorSet <set> 放置在res/animator/目錄下 動畫集合

工作原理

指定時間內,修改屬性(物件中對應的欄位)的值,以此實現該物件在屬性

上的動畫效果。
為了更好的理解,我們舉一個栗子:

圖1.linear animation
圖1中我們搞出了一個假象的物件,動畫作用於這個物件(實際可以說是物件的x屬性,也即物件的水平位置),動畫持續40ms,移動距離40px。每10ms(預設重新整理速率),物件水平移動10px。40ms後,動畫結束,物體停止在x=40處。這是一個典型的設定了linearInterpolator(勻速插值器)的動畫。

為了更好的瞭解屬性動畫的工作原理,下面我們來看一看屬性動畫的元件是怎麼計算上面例子的動畫的。


圖2.動畫計算過程

其邏輯可以總結如下:

  1. 為 ValueAnimator 設定動畫的時長,以及對應屬性的始 & 末值
  2. 設定屬性在 始 & 末值 間的變化邏輯
  • TimeInterpolator實現類:插值器-描述動畫的變化速率
  • TypeEvaluator實現類:估值器-描述 屬性值 變化的具體數值
  1. 根據2中的邏輯更新當前值
  2. 獲取3中更新的 值 ,修改 目標屬性值
  3. 重新整理檢視。
  4. 重複4-5,直到 屬性值 == 末值

下面給出動畫工作的關鍵類

Java類說明
ValueAnimator 動畫執行類;核心
ObjectAnimator 動畫執行類
TimeInterpolator 時間插值(插值器介面),控制動畫變化率
TypeEvaluator 型別估值(估值器介面),設定屬性值計算方式,根據屬性的 始 & 末值 和 插值 一起計算出當前時間的屬性值
AnimatorSet 動畫集
AnimatorInflater 載入屬性動畫的XML檔案

一些額外的類

Java類說明
LayoutTransition 佈局動畫,為佈局的容器設定動畫
ViewPropertyAnimator 為View的動畫操作提供一種更加便捷的用法
PropertyValuesHolder 儲存動畫過程中所需要操作的屬性和對應的值
Keyframe 控制每個時間段執行的動畫距離
AnimationListener
AnimationUpdateListener
AnimatorListenerAdapter
動畫事件的監聽

具體使用

ValueAnimator

  • 屬性動畫的最核心的類
  • 原理:控制 值 的變化,之後 手動 賦值給物件的屬性,從而實現動畫

對於控制的 的不同,Android 提供給我們三種構造方法來例項ValueAnimator物件

  1. ValueAnimator.ofInt(int... values) -- 整型數值
  2. ValueAnimator.ofFloat(float... values) -- 浮點型數值
  3. ValueAnimator.ofObject(TypeEvaluator evaluator, Object... values) -- 自定義物件型別

下面我們一一介紹

ValueAnimator.ofInt()

  • 作用:將初始值 以整型數值的形式 過渡到結束值
  • 估值器:內建IntEvaluator估值器
  • 具體使用:
    操作 值 的方式分為 XML 方式/ Java 程式碼方式

方式1: Java 方式

推薦 Java 方式,因為某些時候我們需要動態獲取屬性的起始值,顯然XML方式是不支援動態獲取的。

//設定動畫 始 & 末值
                //ofInt()兩個作用:
                //1. 獲取例項
                //2. 在傳入引數之間平滑過渡
                //如下則0平滑過渡到3
                ValueAnimator animator = ValueAnimator.ofInt(0,3);
                //如下傳入多個引數,效果則為0->5,5->3,3->10
                //ValueAnimator animator = ValueAnimator.ofInt(0,5,3,10);

                //設定動畫的基礎屬性
                animator.setDuration(5000);//播放時長
                animator.setStartDelay(300);//延遲播放
                animator.setRepeatCount(0);//重放次數
                animator.setRepeatMode(ValueAnimator.RESTART);
                //重放模式
                //ValueAnimator.START:正序
                //ValueAnimator.REVERSE:倒序

                //設定更新監聽
                //值 改變一次,該方法就執行一次
                animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                    @Override
                    public void onAnimationUpdate(ValueAnimator animation) {
                        //獲取改變後的值
                        int currentValue = (int) animation.getAnimatedValue();
                        //輸出改變後的值
                        Log.d("1111", "onAnimationUpdate: " + currentValue);
                        
                        //改變後的值發賦值給物件的屬性值
                        view.setproperty(currentValue);
                        
                        //重新整理檢視
                        view.requestLayout();
                    }
                });
                //啟動動畫
                animator.start();

以上就是一個標準的Java方式的模板

方式2: XML 方式

  1. 在路徑 res/animator/ 路徑下常見 XML 檔案,如 set_animator.xml
  2. 在上述檔案中設定動畫引數
// ValueAnimator採用<animator>  標籤
<animator xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="1000"
    android:valueFrom="1"
    android:valueTo="0"
    android:valueType="floatType"
    android:repeatCount="1"
    android:repeatMode="reverse"/>
/>
  1. Java程式碼啟動動畫
Animator animator = AnimatorInflater.loadAnimator(context, R.animator.set_animation);  
// 載入XML動畫

animator.setTarget(view);  
// 設定動畫物件

animator.start();  
// 啟動動畫

ValueAnimator.ofFloat()

  • 作用:將初始值 以浮點型數值的形式 過渡到結束值
  • 估值器:內建FloatEvaluator估值器
  • 具體使用:
    和ValueAnimator.ofInt()及其類似,以下只說明不同之處,省略部分參考 ofInt()

方式1:Java方式

ValueAnimator anim = ValueAnimator.ofFloat(0, 3);  
//只是改了例項方法,除此之外完全一樣

方式2:XML方式
只在設定動畫 XML 檔案中的屬性時略有不同

// ValueAnimator 採用 <animator>  標籤
// ObjectAnimator 採用 <objectAnimator> 標籤
<animatorxmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="1000"
    android:valueTo="200"
    android:valueType="floatType"
    android:propertyName="y"
    android:repeatCount="1"
    android:repeatMode="reverse"/>

ValueAnimator.ofObject()

  • 作用:將初始值 以物件的形式 過渡到結束值
  • 估值器:Android 不提供,需要自定義估值器
  • 具體使用:
    ValueAnimator.ofObject() 屬於 ValueAnimator 的高階用法,我們前面提到的對任意物件進行動畫操作,就是通過此方法實現的。
    以下先放上示例模板:
// 建立初始動畫的物件  & 結束動畫的物件
Point point1 = new Point ();  
Point point2 = new Point ();  

// 建立動畫物件 & 設定引數
ValueAnimator anim = ValueAnimator.ofObject(new myObjectEvaluator(), point1 , point2 );  
// 引數說明
// 1. 自定義的估值器物件(TypeEvaluator 型別引數) - 下面會詳細介紹
// 2. 初始動畫的物件
// 3. 結束動畫的物件
anim.setDuration(length);  
anim.start();

上面示例中,我們看到有兩個Point物件point1 , point2 。假設我們有一個自定義View,這個View中有一個Point物件用於管理座標,然後我們在onDraw()方法中根據這個Point物件的座標值進行繪製,也就是說,我們可以對Point物件進行動畫操作,不停的根據Point的座標重新整理View重繪製,以此就可以實現 View 的動畫了。
到這裡都很好理解,不過我們還注意到傳入了一個 new myObjectEvaluator()(TypeEvaluator實現類) 引數,這是幹什麼的呢?不要急,下面我們就詳細解答。

TypeEvaluator 估值器

其實我們已經不止一次的提到估值器的概念,如 ValueAnimator.ofFloat() 方法中的 IntEvaluatorValueAnimator.ofFloat() 方法中的 FloatEvaluator,這兩種都是Android預置供我們使用的,二者通過計算告知動畫系統如何從初始值過渡到結束值。
但是二者雖然好用,但也有其侷限性:只能針對 Int / Float 型別數值操作。因此某些我們需要對任意物件進行動畫操作的時候,二者顯然不能滿足我們的需求了,這時候我們需要自定義一個TypeEvaluator來告知系統如何進行過渡。

那麼如何自定義呢?別急,我們可以先看一看系統提供的 IntEvaluator 是如何實現的:

/**
 * This evaluator can be used to perform type interpolation between int values.
 */
public class IntEvaluator implements TypeEvaluator<Integer> {

    /**
     * This function returns the result of linearly interpolating the start and end values, with
     * fraction representing the proportion between the start and end values. The
     * calculation is a simple parametric calculation: result = x0 + t * (v1 - v0),
     * where x0 is startValue, x1 is endValue, and t is fraction.
     *
     * @param fraction   The fraction from the starting to the ending values
     * @param startValue The start value; should be of type int or Integer
     * @param endValue   The end value; should be of type int or Integer
     * @return A linear interpolation between the start and end values, given the fraction parameter.
     */
    public Integer evaluate(float fraction, Integer startValue, Integer endValue) {
        int startInt = startValue;
        return (int)(startInt + fraction * (endValue - startInt));
    }
}

原始碼很簡單,註釋也相當詳細,方便觀看,我再次解釋一下:
IntEvaluator 實現了 TypeEvaluator 介面,然後重寫了 evaluate() 方法。該方法的三個引數意義如下:

  • fraction:表示動畫完成度,據此計算當前動畫的值
  • startValue:動畫初始值
  • endValue:動畫結束值

那麼 evalute() 方法的返回值就不難理解了,簡單的數學公式,返回當前動畫的值。

是不是很簡單?因此我們自定義 TypeEvaluator 時只需要實現 TypeEvaluator 介面,然後重寫 evaluate() 方法,在此方法中處理好邏輯即可。
下面我們就動手寫一個自定義 TypeEvaluator

自定義TypeEvaluator

我們還是以上面提過的Point物件管理View座標的為例:

  1. 定義Point
public class Point {
    //記錄座標位置
    private float x;
    private float y;

    //通過構造方法設定座標,因此不需要額外的set方法
    public Point(float x, float y) {
        this.x = x;
        this.y = y;
    }

    //get方法,獲取當前座標值
    public float getX() {
        return x;
    }
    
    public float getY() {
        return y;
    }
}
  1. 定義 PointEvaluator
//實現TypeEvaluator介面
public class PointEvaluator implements TypeEvaluator {

    //重寫evaluate()方法
    @Override
    public Object evaluate(float fraction, Object startValue, Object endValue) {

        //始末值強轉為Point物件
        Point startPoint = (Point) startValue;
        Point endPoint = (Point) endValue;

        //通過fraction計算當前動畫的座標值x,y
        float x = startPoint.getX() + fraction * (endPoint.getX() - startPoint.getX());
        float y = startPoint.getY() + fraction * (endPoint.getY() - startPoint.getY());

        //返回以上述x,y組裝的新的Point物件
        Point point = new Point(x,y);
        return point;
    }
}
  1. Point 物件間的平滑過渡
// 建立初始動畫的物件  & 結束動畫的物件
Point point1 = new Point(0, 0);  
Point point2 = new Point(500, 500);  

// 建立動畫物件 & 設定引數
ValueAnimator anim = ValueAnimator.ofObject(new PointEvaluator(), point1 , point2 );  
anim.setDuration(3000);
anim.start();

以上就是自定義TypeEvaluator的全部用法。
下面我們就可以嘗試用上述知識練習如何對物件進行動畫操作,從而實現自定義View的動畫效果

自定義View的動畫效果

  1. 新建MyAnimView繼承View
public class MyAnimView extends View {

    //常量
    public static final float RADIUS = 50f;

    //當前Point,記錄當前動畫的值(x,y座標)
    private Point curPoint;

    //畫筆
    private Paint mPaint;

    //Java程式碼例項化View時呼叫
    public MyAnimView(Context context) {
        super(context);
    }
    //XML檔案例項時呼叫
    public MyAnimView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        //初始化畫筆
        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mPaint.setColor(Color.BLUE);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        //第一次繪製時
        if (curPoint == null){
            //初始化座標為(50f, 50f)
            curPoint = new Point(RADIUS,RADIUS);
            //畫圓
            drawCircle(canvas);
            //開始動畫
            startAnimation();
        }else {//非第一次繪製
            drawCircle(canvas);
        }
    }

    //在當前座標處繪製一個半徑為50f的圓
    private void drawCircle(Canvas canvas) {
        float x = curPoint.getX();
        float y = curPoint.getY();
        canvas.drawCircle(x,y,RADIUS,mPaint);
    }

    //開始動畫
    private void startAnimation() {
        //設定 起始值&結束值
        Point startPoint = new Point(RADIUS,RADIUS);//起始為左上角(50f,50f)
        Point endPoint = new Point(getWidth() - RADIUS, getHeight() - RADIUS);//終點為右下角(螢幕寬度-50f,螢幕高度-50f)
        final ValueAnimator anim = ValueAnimator.ofObject(new PointEvaluator(),startPoint,endPoint);
        //設定插值器
        anim.setInterpolator(new BounceInterpolator());
        //設定監聽
        anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {

            //每當Point的值有改變的時候,都會呼叫onAnimationUpdate()方法
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                //更新curPoint,即更新當前座標
                curPoint = (Point) animation.getAnimatedValue();
                // 重新整理,重現呼叫onDraw()方法
                // 由於curPoint的值改變,那麼繪製的位置也會改變,也就實現了一個從左上到右下的平移動畫
                invalidate();
            }
        });
        anim.setDuration(5000);
        anim.start();
    }
}
  1. 佈局檔案中引入該自定義view
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical" android:layout_width="match_parent"
    android:layout_height="match_parent">
    <com.example.whdalive.learn_propertyanimation.Property.MyAnimView
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
</LinearLayout>

效果演示


上圖中我們發現球並非勻速,而是有一個bounce的效果,這是因為我們為動畫添加了一個插值器 anim.setInterpolator(new BounceInterpolator())
具體Interpolator的使用,我們在前一篇Android 檢視動畫(View Animation) 使用詳解中已經詳細講解了,此處就不再贅述。

注意
ValueAnimator.ofObject()的本質還是操作 , 只是將 多個值 封裝到一個物件裡,同時對 該物件 裡的 多個值 一起操作而已

至此,ValueAnimator 的用法就介紹完了,下面我們介紹一個貌似更加常用的類 ObjectAnimator

ObjectAnimator

  • 屬性動畫重要的類
  • 原理:控制 值 的變化,之後 自動 賦給物件的屬性,從而實現動畫
  • 與ValueAnimator對比
    • ValueAnimator的子類
    • ValueAnimator只是對 進行平滑的動畫過渡;ObjectAnimator直接對 任意物件的任意屬性 進行動畫操作,如View的alpha屬性
    • ValueAnimator需要我們為物件屬性手動賦值;ObjectAnimator會為物件屬性自動賦值

具體使用

由於繼承關係的存在,ObjectAnimator的用法和ValueAnimator及其類似:Java方式設定 / XML方式設定(推薦Java方式)

方式1:Java方式

ObjectAnimator animator = ObjectAnimator.ofFloat(Object object, String property, float ....values);  
//ObjectAnimator animator = ObjectAnimator.ofInt(Object object, String property, int ....values);  
//ObjectAnimator animator = ObjectAnimator.ofObject(Object object, String property, TypeEvaluator evaluator,Object....values);  

// 以ofFloat為例 引數說明:
// Object object:需要操作的物件
// String property:需要操作的物件的屬性
// float ....values:動畫初始值 & 結束值(不固定長度)
// 若是兩個引數a,b,則動畫效果則是從屬性的a值到b值
// 若是三個引數a,b,c,則則動畫效果則是從屬性的a值到b值再到c值
// 以此類推
// 至於如何從初始值 過渡到 結束值,同樣是由估值器決定,此處ObjectAnimator.ofFloat()是有系統內建的浮點型估值器FloatEvaluator,同ValueAnimator講解

//動畫基本屬性
anim.setDuration(500); 
anim.setStartDelay(500);
anim.setRepeatCount(0);
anim.setRepeatMode(ValueAnimator.RESTART);

animator.start();  
// 啟動動畫

方式2:XML方式設定

  1. 路徑res/animator/下建立動畫XML檔案,如set_animation.xml
  2. 設定動畫引數
ObjectAnimator 採用<objectAnimator >  標籤
<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"  
    android:valueFrom="1"   // 初始值
    android:valueTo="0"  // 結束值
    android:valueType="floatType"  // 變化值型別 :floatType & intType
    android:propertyName="alpha" // 物件變化的屬性名稱
/>
  1. Java啟動動畫
Animator animator = AnimatorInflater.loadAnimator(context, R.animator.view_animation);  
// 載入XML動畫
animator.setTarget(view);  
// 設定動畫物件
animator.start();  
// 啟動動畫

以上為ObjectAnimator的標準使用模板,我們通過控制傳入 ofFloat()的第二個引數preperty 或者 XML中的android:propertyName屬性 來產生不同動畫效果,以下為Android預置好的一些屬性:即四種基本變換,透明度、平移、縮放、旋轉

屬性作用數值型別
alpha 透明度 float
translationX X方向的位移 float
translationY Y方向的位移 float
scaleX X方向的縮放倍數 float
scaleY Y方向的縮放倍數 float
rotation 以螢幕方向為軸的旋轉度數 float
rotationX 以X軸為軸的旋轉度數 float
rotationY 以Y軸為軸的旋轉度數 float

那麼問題來了,除了以上的屬性,我們還可以傳入哪些屬性值呢?
答案是 任意屬性值

為了理解這個問題,我們需要先了解一下ObjectAnimator是如何自動為屬性賦值的。

自動賦值
  • 以alpha+textview為例,很顯然textview是不具備alpha這一屬性的,那麼ObjectAnimator是如何操作的呢?
  • 原理:實際上ObjectAnimator內部的工作機制是去尋找這個屬性名對應的get和set方法,通過二者賦值。
ObjectAnimator animator = ObjectAnimator.ofFloat(textview, "alpha",1f, 0f, 1f);
// TextView物件中並沒有alpha這個屬性值
// ObjectAnimator並不是直接對我們傳入的屬性名進行操作
// 而是根據傳入的屬性值"alpha" 去尋找物件對應屬性名對應的get和set方法,從而通過set() &  get()對屬性進行賦值

// 因為TextView物件中有alpha屬性所對應的get & set方法
// 所以傳入的alpha屬性是有效的
// 所以才能對alpha這個屬性進行操作賦值
public void setRotation(float value);  
public float getRotation();  

// 實際上,這兩個方法是由View物件提供的,其餘平移旋轉等屬性同理

因此:

  • ObjectAnimator 類針對的是任意物件 & 任意屬性值,並不是單單針對於View物件
  • 如果需要採用ObjectAnimator 類實現動畫效果,那麼需要操作的物件就必須有該屬性的set() & get()

特別注意:如果想讓物件的屬性a的動畫生效,屬性a需要同時滿足下面兩個條件:

  1. 物件必須要提供屬性a的set()方法
    a. 如果沒傳遞初始值,那麼需要提供get()方法,因為系統要去拿屬性a的初始值
    b. 若該條件不滿足,程式直接崩潰
  2. 物件提供的 屬性a的set()方法 對 屬性a的改變 必須通過某種方法反映出來
    a. 如帶來ui上的變化
    b. 若這條不滿足,動畫無效,但不會崩潰)
自定義物件實現動畫效果

本質:

  • 為物件設定需要操作屬性的set() & get()方法
  • 通過實現TypeEvaluator類從而定義屬性變化的邏輯

下面我們通過例項來介紹如何通過自定義屬性實現動畫效果

  • 實現效果(在前面運動小球的基礎上控制顏色變化)

    untitled.gif

  • 具體實現

  1. 為物件類屬性color設定set() & get() 方法
public class MyAnimView extends View {
    
    ...
    
    //將顏色設定為String型別,使用#RRGGBB格式表示顏色
    private String color;

    //get()方法
    public String getColor() {
        return color;
    }

    //set()方法
    public void setColor(String color) {
        mPaint.setColor(Color.parseColor(color));
        this.color = color;
        //改變畫筆顏色後立即重新整理檢視,然後onDraw()方法就會呼叫
        invalidate();
    }
    ...
}

  1. 佈局中加入自定義view
    同前面ValueAnimator的自定義view
  2. 根據需求實現TypeEvaluator介面
public class ColorEvaluator implements TypeEvaluator {
    // 實現TypeEvaluator介面

    private int mCurrentRed;

    private int mCurrentGreen ;

    private int mCurrentBlue ;

    // 複寫evaluate()
    // 在evaluate()裡寫入物件動畫過渡的邏輯:此處是寫顏色過渡的邏輯
    @Override
    public Object evaluate(float fraction, Object startValue, Object endValue) {

        // 獲取到顏色的初始值和結束值
        String startColor = (String) startValue;
        String endColor = (String) endValue;

        // 通過字串擷取的方式將初始化顏色分為RGB三個部分,並將RGB的值轉換成十進位制數字
        // 那麼每個顏色的取值範圍就是0-255
        int startRed = Integer.parseInt(startColor.substring(1, 3), 16);
        int startGreen = Integer.parseInt(startColor.substring(3, 5), 16);
        int startBlue = Integer.parseInt(startColor.substring(5, 7), 16);

        int endRed = Integer.parseInt(endColor.substring(1, 3), 16);
        int endGreen = Integer.parseInt(endColor.substring(3, 5), 16);
        int endBlue = Integer.parseInt(endColor.substring(5, 7), 16);

        // 將初始化顏色的值定義為當前需要操作的顏色值
            mCurrentRed = startRed;
            mCurrentGreen = startGreen;
            mCurrentBlue = startBlue;


        // 計算初始顏色和結束顏色之間的差值
        // 該差值決定著顏色變化的快慢:初始顏色值和結束顏色值很相近,那麼顏色變化就會比較緩慢;否則,變化則很快
        // 具體如何根據差值來決定顏色變化快慢的邏輯寫在getCurrentColor()裡.
        int redDiff = Math.abs(startRed - endRed);
        int greenDiff = Math.abs(startGreen - endGreen);
        int blueDiff = Math.abs(startBlue - endBlue);
        int colorDiff = redDiff + greenDiff + blueDiff;
        if (mCurrentRed != endRed) {
            mCurrentRed = getCurrentColor(startRed, endRed, colorDiff, 0,
                    fraction);
                    // getCurrentColor()決定如何根據差值來決定顏色變化的快慢 ->>關注1
        } else if (mCurrentGreen != endGreen) {
            mCurrentGreen = getCurrentColor(startGreen, endGreen, colorDiff,
                    redDiff, fraction);
        } else if (mCurrentBlue != endBlue) {
            mCurrentBlue = getCurrentColor(startBlue, endBlue, colorDiff,
                    redDiff + greenDiff, fraction);
        }
        // 將計算出的當前顏色的值組裝返回
        String currentColor = "#" + getHexString(mCurrentRed)
                + getHexString(mCurrentGreen) + getHexString(mCurrentBlue);

        // 由於我們計算出的顏色是十進位制數字,所以需要轉換成十六進位制字串:呼叫getHexString()->>關注2
        // 最終將RGB顏色拼裝起來,並作為最終的結果返回
        return currentColor;
    }


    // 關注1:getCurrentColor()
    // 具體是根據fraction值來計算當前的顏色。

    private int getCurrentColor(int startColor, int endColor, int colorDiff,
                                int offset, float fraction) {
        int currentColor;
        if (startColor > endColor) {
            currentColor = (int) (startColor - (fraction * colorDiff - offset));
            if (currentColor < endColor) {
                currentColor = endColor;
            }
        } else {
            currentColor = (int) (startColor + (fraction * colorDiff - offset));
            if (currentColor > endColor) {
                currentColor = endColor;
            }
        }
        return currentColor;
    }

    // 關注2:將10進位制顏色值轉換成16進位制。
    private String getHexString(int value) {
        String hexString = Integer.toHexString(value);
        if (hexString.length() == 1) {
            hexString = "0" + hexString;
        }
        return hexString;
    }

}
  1. 呼叫ObjectAnimator.ofObject()方法
//藉助AnimatorSet,實現組合動畫
public class MyAnimView extends View {
     ...
    //開始動畫
    private void startAnimation() {
        //設定 起始值&結束值
        Point startPoint = new Point(RADIUS,RADIUS);//起始為左上角(50f,50f)
        Point endPoint = new Point(getWidth() - RADIUS, getHeight() - RADIUS);//終點為右下角(螢幕寬度-50f,螢幕高度-50f)
        final ValueAnimator anim = ValueAnimator.ofObject(new PointEvaluator(),startPoint,endPoint);
        //設定插值器
        anim.setInterpolator(new BounceInterpolator());
        //設定監聽
        anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {

            //每當Point的值有改變的時候,都會呼叫onAnimationUpdate()方法
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                //更新curPoint,即更新當前座標
                curPoint = (Point) animation.getAnimatedValue();
                // 重新整理,重現呼叫onDraw()方法
                // 由於curPoint的值改變,那麼繪製的位置也會改變,也就實現了一個從左上到右下的平移動畫
                invalidate();
            }
        });
       // anim.setDuration(5000);
        //anim.start();
        

        //顏色過渡的程式碼邏輯放在startAnimation()方法中,本身就在MyAnimView中執行,因此傳入 this 引數即可
        ObjectAnimator anim2 = ObjectAnimator.ofObject(this,"color",new ColorEvaluator(),"#FF0000", "#0000FF");
        //建立一個AnimatorSet,讓兩個動畫同時播放,時長5s
        AnimatorSet animationSet = new AnimatorSet();
        animationSet.play(anim).with(anim2);
        animationSet.setDuration(5000);
        //啟動動畫
        animationSet.start();
    }

以上我們就實現了一個平移 & 顏色變化的動畫。
至此關於ValueAnimator和ObjectAnimeato的使用就講解完了,下面我們再講講一些額外的使用方法

1.AnimatorSet 組合動畫

  • 獨立的動畫能夠實現的視覺效果畢竟是相當有限的,因此將多個動畫組合到一起播放就顯得尤為重要
  • 使用:
AnimatorSet.play(Animator anim)   :播放當前動畫
AnimatorSet.after(long delay)   :將現有動畫延遲x毫秒後執行
AnimatorSet.with(Animator anim)   :將現有動畫和傳入的動畫同時執行
AnimatorSet.after(Animator anim)   :將現有動畫插入到傳入的動畫之後執行
AnimatorSet.before(Animator anim) :  將現有動畫插入到傳入的動畫之前執行
  • 例項
    Java方式
ObjectAnimator a1 = ObjectAnimator.ofFloat(view, "alpha", 1.0f, 0f);  
ObjectAnimator a2 = ObjectAnimator.ofFloat(view, "translationY", 0f, viewWidth);  
......
AnimatorSet animSet = new AnimatorSet();  
animSet.setDuration(5000);  
animSet.setInterpolator(new LinearInterpolator());   
//animSet.playTogether(a1, a2, ...); //兩個動畫同時執行  
animSet.play(a1).after(a2); //先後執行
......//其他組合方式
animSet.start();  

XML方式

<set
  android:ordering=["together" | "sequentially"]>

    <objectAnimator
        android:propertyName="string"
        android:duration="int"
        android:valueFrom="float | int | color"
        android:valueTo="float | int | color"
        android:startOffset="int"
        android:repeatCount="int"
        android:repeatMode=["repeat" | "reverse"]
        android:valueType=["intType" | "floatType"]/>

    <animator
        android:duration="int"
        android:valueFrom="float | int | color"
        android:valueTo="float | int | color"
        android:startOffset="int"
        android:repeatCount="int"
        android:repeatMode=["repeat" | "reverse"]
        android:valueType=["intType" | "floatType"]/>

    <set>
        ...
    </set>
</set>

2. 監聽動畫

  • 監聽到動畫的各種事件,比如動畫何時開始,何時結束,然後在開始或者結束的時候去執行一些邏輯處理
  • Animator類提供addListener()方法,說明其子類都可以使用該方法(關於繼承關係,前面我們提到過了)
  • 使用方法
anim.addListener(new AnimatorListener() {
          @Override
          public void onAnimationStart(Animation animation) {
              //動畫開始時執行
          }
      
           @Override
          public void onAnimationRepeat(Animation animation) {
              //動畫重複時執行
          }

         @Override
          public void onAnimationCancel()(Animation animation) {
              //動畫取消時執行
          }
    
          @Override
          public void onAnimationEnd(Animation animation) {
              //動畫結束時執行
          }
      });

// 特別注意:每次監聽必須4個方法都重寫。
  • 缺點:
    很多時候我們並不想要監聽那麼多個事件,可能我只想要監聽動畫結束這一個事件,那麼每次都要將四個介面全部實現一遍就顯得非常繁瑣。
  • 如何解決
    採用動畫介面卡(AnimatorListenerAdapter),解決 實現介面繁瑣 的問題
anim.addListener(new AnimatorListenerAdapter() {  
// 向addListener()方法中傳入介面卡物件AnimatorListenerAdapter()
// 由於AnimatorListenerAdapter中已經實現好每個介面
// 所以這裡不實現全部方法也不會報錯
    @Override  
    public void onAnimationStart(Animator animation) {  
    // 如想只想監聽動畫開始時刻,就只需要單獨重寫該方法就可以
    }  
});

3. ViewPropertyAnimator用法

  • Google為View的動畫操作提供的一種便捷用法
  • 具體使用:
// 使用解析
        View.animate().xxx().xxx();
        // ViewPropertyAnimator的功能建立在animate()上
        // 呼叫animate()方法返回值是一個ViewPropertyAnimator物件,之後的呼叫的所有方法都是通過該例項完成
        // 呼叫該例項的各種方法來實現動畫效果
        // ViewPropertyAnimator所有介面方法都使用連綴語法來設計,每個方法的返回值都是它自身的例項
        // 因此呼叫完一個方法後可直接連綴呼叫另一方法,即可通過一行程式碼就完成所有動畫效果
        
// 以下是例子
        mButton = (Button) findViewById(R.id.Button);
        // 建立動畫作用物件:此處以Button為例

        mButton.animate().alpha(0f);
        // 單個動畫設定:將按鈕變成透明狀態 
        mButton.animate().alpha(0f).setDuration(5000).setInterpolator(new BounceInterpolator());
        // 單個動畫效果設定 & 引數設定 
        mButton.animate().alpha(0f).x(500).y(500);
        // 組合動畫:將按鈕變成透明狀態再移動到(500,500)處
        
        // 特別注意:
        // 動畫自動啟動,無需呼叫start()方法.因為新的介面中使用了隱式啟動動畫的功能,只要我們將動畫定義完成後,動畫就會自動啟動
        // 該機制對於組合動畫也同樣有效,只要不斷地連綴新的方法,那麼動畫就不會立刻執行,等到所有在ViewPropertyAnimator上設定的方法都執行完畢後,動畫就會自動啟動
        // 如果不想使用這一預設機制,也可以顯式地呼叫start()方法來啟動動畫
  • 注意事項
    • ViewPropertyAnimator例項通過View.animate()方法建立,之後的呼叫的所有方法,設定的所有屬性都是通過這個例項完成的。
    • ViewPropertyAnimator隱式啟動動畫,即當我們將動畫定義完成之後,動畫就會自動啟動。當然,如果需要手動顯式啟動,我們仍然可以呼叫start()方法啟動動畫。
    • ViewPropertyAnimator的連綴語法:每個方法返回值都是自身例項,因此方法的呼叫可以連綴呼叫,如上面示例程式碼中寫的那樣。

至此,關於屬性動畫能想到的就全都講解完了,至於有所遺漏的,留作日後再補充吧~


轉載自
作者:whd_Alive
連結:https://www.jianshu.com/p/a480ca619dd9