Android動畫:這裏有一份很詳細的 屬性動畫 使用攻略


前言

  • 動畫的使用 是 Android 開發中經常使用的知識
  • 本文將詳細介紹 Android 動畫中 屬性動畫的原理 & 使用

動畫類型


目錄

目錄


1. 屬性動畫出現的緣由

  • 屬性動畫(Property Animation)是在 Android 3.0API 11)後才提供的一種全新動畫模式
  • 那麼爲何要提供屬性動畫(Property Animation)?

1.1 背景

實現動畫效果在Android開發中很是常見,所以Android系統一開始就提供了兩種實現動畫的方式:java

  • 逐幀動畫(Frame Animation
  • 補間動畫( Tweened animation

1.2 問題

逐幀動畫 & 補間動畫存在必定的缺點:android

a. 做用對象侷限:View

即補間動畫 只可以做用在視圖View上,即只能夠對一個ButtonTextView、甚至是LinearLayout、或者其它繼承自View的組件進行動畫操做,但沒法對非View的對象進行動畫操做git

  1. 有些狀況下的動畫效果只是視圖的某個屬性 & 對象而不是整個視圖;
  2. 如,現須要實現視圖的顏色動態變化,那麼就須要操做視圖的顏色屬性從而實現動畫效果,而不是針對整個視圖進行動畫操做
b. 沒有改變View的屬性,只是改變視覺效果
  • 補間動畫只是改變了View的視覺效果,而不會真正去改變View的屬性。
  • 如,將屏幕左上角的按鈕 經過補間動畫 移動到屏幕的右下角
  • 點擊當前按鈕位置(屏幕右下角)是沒有效果的,由於實際上按鈕仍是停留在屏幕左上角,補間動畫只是將這個按鈕繪製到屏幕右下角,改變了視覺效果而已。
c. 動畫效果單一
  • 補間動畫只能實現平移、旋轉、縮放 & 透明度這些簡單的動畫需求
  • 一旦遇到相對複雜的動畫效果,即超出了上述4種動畫效果,那麼補間動畫則沒法實現。

即在功能 & 可擴展性有較大侷限性github

1.3 問題

  • 爲了解決補間動畫的缺陷,在 Android 3.0(API 11)開始,系統提供了一種全新的動畫模式:屬性動畫(Property Animation
  • 下面,我將全面介紹屬性動畫(Property Animation

2. 簡介

  • 做用對象:任意 Java 對象

再也不侷限於 視圖View對象算法

  • 實現的動畫效果:可自定義各類動畫效果

再也不侷限於4種基本變換:平移、旋轉、縮放 & 透明度canvas


3. 特色

  • 做用對象進行了擴展:不僅是View對象,甚至沒對象也能夠
  • 動畫效果:不僅是4種基本變換,還有其餘動畫效果
  • 做用領域:API11後引入的

4. 工做原理

  • 在必定時間間隔內,經過不斷對值進行改變,並不斷將該值賦給對象的屬性,從而實現該對象在該屬性上的動畫效果

能夠是任意對象的任意屬性設計模式

  • 具體的工做原理邏輯以下:

工做原理

  • 從上述工做原理能夠看出屬性動畫有兩個很是重要的類:ValueAnimator 類 & ObjectAnimator
  • 其實屬性動畫的使用基本都是依靠這兩個類
  • 因此,在下面介紹屬性動畫的具體使用時,我會着重介紹這兩個類。

5. 具體使用

5.1 ValueAnimator類

  • 定義:屬性動畫機制中 最核心的一個類
  • 實現動畫的原理:經過不斷控制 值 的變化,再不斷 手動 賦給對象的屬性,從而實現動畫效果。如圖下:

工做原理

從上面原理能夠看出:ValueAnimator類中有3個重要方法:bash

  1. ValueAnimator.ofInt(int values)
  2. ValueAnimator.ofFloat(float values)
  3. ValueAnimator.ofObject(int values)

下面我將逐一介紹。微信

5.1.1 ValueAnimator.ofInt(int values)

  • 做用:將初始值 以整型數值的形式 過渡到結束值

即估值器是整型估值器 - IntEvaluatorapp

  • 工做原理:

工做原理

  • 具體使用:

特別說明:

  1. 由於ValueAnimator本質只是一種值的操做機制,因此下面的介紹先是展現如何改變一個值的過程(下面的實例主要講解:如何將一個值從0平滑地過渡到3)
  2. 至於如何實現動畫,是須要開發者手動將這些 值 賦給 對象的屬性值。關於這部分在下節會進行說明。

操做值的方式 分爲 XML 設置 / Java 代碼設置 設置方式1:Java代碼設置

實際開發中,建議使用Java代碼實現屬性動畫:由於不少時候屬性的起始值是沒法提早肯定的(沒法使用XML設置),這就須要在Java代碼裏動態獲取。

// 步驟1:設置動畫屬性的初始值 & 結束值
ValueAnimator anim = ValueAnimator.ofInt(0, 3);
        // ofInt()做用有兩個
        // 1. 建立動畫實例
        // 2. 將傳入的多個Int參數進行平滑過渡:此處傳入0和1,表示將值從0平滑過渡到1
        // 若是傳入了3個Int參數 a,b,c ,則是先從a平滑過渡到b,再從b平滑過渡到C,以此類推
        // ValueAnimator.ofInt()內置了整型估值器,直接採用默認的.不須要設置,即默認設置瞭如何從初始值 過渡到 結束值
        // 關於自定義插值器我將在下節進行講解
        // 下面看看ofInt()的源碼分析 ->>關注1
        
// 步驟2:設置動畫的播放各類屬性
        anim.setDuration(500);
        // 設置動畫運行的時長
        
        anim.setStartDelay(500);
        // 設置動畫延遲播放時間

        anim.setRepeatCount(0);
        // 設置動畫重複播放次數 = 重放次數+1
        // 動畫播放次數 = infinite時,動畫無限重複
        
        anim.setRepeatMode(ValueAnimator.RESTART);
        // 設置重複播放動畫模式
        // ValueAnimator.RESTART(默認):正序重放
        // ValueAnimator.REVERSE:倒序回放
     
// 步驟3:將改變的值手動賦值給對象的屬性值:經過動畫的更新監聽器
        // 設置 值的更新監聽器
        // 即:值每次改變、變化一次,該方法就會被調用一次
        anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {

                int currentValue = (Integer) animation.getAnimatedValue();
                // 得到改變後的值
                
                System.out.println(currentValue);
                // 輸出改變後的值

        // 步驟4:將改變後的值賦給對象的屬性值,下面會詳細說明
                View.setproperty(currentValue);

       // 步驟5:刷新視圖,即從新繪製,從而實現動畫效果
                View.requestLayout();
                
                
            }
        });

        anim.start();
        // 啓動動畫
    }

// 關注1:ofInt()源碼分析
    public static ValueAnimator ofInt(int... values) {
        // 容許傳入一個或多個Int參數
        // 1. 輸入一個的狀況(如a):從0過渡到a;
        // 2. 輸入多個的狀況(如a,b,c):先從a平滑過渡到b,再從b平滑過渡到C
        
        ValueAnimator anim = new ValueAnimator();
        // 建立動畫對象
        anim.setIntValues(values);
        // 將傳入的值賦值給動畫對象
        return anim;
    }
複製代碼

效果圖

值 從初始值 過分到 結束值 的過程以下:

效果圖

**設置方法2:在XML 代碼中設置 ** 具有重用性,即將通用的動畫寫到XML裏,可在各個界面中去重用它

  • 步驟1:在路徑 res/animator的文件夾裏建立相應的動畫 .xml文件

此處設置爲res/animator/set_animation.xml

  • 步驟2:設置動畫參數

set_animation.xml

// ValueAnimator採用<animator>  標籤
<animator xmlns:android="http://schemas.android.com/apk/res/android"  
    android:valueFrom="0"   // 初始值
    android:valueTo="100"  // 結束值
    android:valueType="intType" // 變化值類型 :floatType & intType

    android:duration="3000" // 動畫持續時間(ms),必須設置,動畫纔有效果
    android:startOffset ="1000" // 動畫延遲開始時間(ms)
    android:fillBefore = 「true」 // 動畫播放完後,視圖是否會停留在動畫開始的狀態,默認爲true
    android:fillAfter = 「false」 // 動畫播放完後,視圖是否會停留在動畫結束的狀態,優先於fillBefore值,默認爲false
    android:fillEnabled= 「true」 // 是否應用fillBefore值,對fillAfter值無影響,默認爲true
    android:repeatMode= 「restart」 // 選擇重複播放動畫模式,restart表明正序重放,reverse表明倒序回放,默認爲restart|
    android:repeatCount = 「0」 // 重放次數(因此動畫的播放次數=重放次數+1),爲infinite時無限重複
    android:interpolator = @[package:]anim/interpolator_resource // 插值器,即影響動畫的播放速度,下面會詳細講

/>  
複製代碼
  • 步驟3:在Java代碼中啓動動畫
Animator animator = AnimatorInflater.loadAnimator(context, R.animator.set_animation);  
// 載入XML動畫

animator.setTarget(view);  
// 設置動畫對象

animator.start();  
// 啓動動畫
複製代碼

效果圖

效果同第一種方式是同樣的。

實例說明

  • 下面,我將結合 手動賦值給對象屬性 這一步驟,從而實現一個完整的動畫效果
  • 實現的動畫效果:按鈕的寬度從 150px 放大到 500px
Button mButton = (Button) findViewById(R.id.Button);
        // 建立動畫做用對象:此處以Button爲例

// 步驟1:設置屬性數值的初始值 & 結束值
        ValueAnimator valueAnimator = ValueAnimator.ofInt(mButton.getLayoutParams().width, 500);
        // 初始值 = 當前按鈕的寬度,此處在xml文件中設置爲150
        // 結束值 = 500
        // ValueAnimator.ofInt()內置了整型估值器,直接採用默認的.不須要設置
        // 即默認設置瞭如何從初始值150 過渡到 結束值500

// 步驟2:設置動畫的播放各類屬性
        valueAnimator.setDuration(2000);
        // 設置動畫運行時長:1s

// 步驟3:將屬性數值手動賦值給對象的屬性:此處是將 值 賦給 按鈕的寬度
        // 設置更新監聽器:即數值每次變化更新都會調用該方法
        valueAnimator.addUpdateListener(new AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animator) {

                int currentValue = (Integer) animator.getAnimatedValue();
                // 得到每次變化後的屬性值
                System.out.println(currentValue);
                // 輸出每次變化後的屬性值進行查看

                mButton.getLayoutParams().width = currentValue;
                // 每次值變化時,將值手動賦值給對象的屬性
                // 即將每次變化後的值 賦 給按鈕的寬度,這樣就實現了按鈕寬度屬性的動態變化

// 步驟4:刷新視圖,即從新繪製,從而實現動畫效果
                mButton.requestLayout();
                
            }
        });

        valueAnimator.start();
        // 啓動動畫

    }
複製代碼

效果圖

效果圖

5.1.2 ValueAnimator.oFloat(float values)

  • 做用:將初始值 以浮點型數值的形式 過渡到結束值

  • 工做原理:

工做原理

  • 具體使用:分爲 XML 設置 / Java 代碼設置

**設置方法1:在 Java 代碼中設置 **

ValueAnimator anim = ValueAnimator.ofFloat(0, 3);  
// 其餘使用相似ValueAnimator.ofInt(int values),此處不做過多描述
複製代碼

**設置方法2:在XML 代碼中設置 **

  • 步驟1:在路徑 res/animator的文件夾裏建立相應的動畫.xml文件

此處設置爲res/animator/set_animation.xml

  • 步驟2:設置動畫參數

set_animation.xml

// ValueAnimator採用<animator>  標籤
<animator xmlns:android="http://schemas.android.com/apk/res/android"  
    // 設置屬性同上
    android:valueFrom="0"  
    android:valueTo="100"  
    android:valueType="intType"/>  
複製代碼
  • 步驟3:在Java代碼中啓動動畫
Animator animator = AnimatorInflater.loadAnimator(context, R.animator.set_animation);  
// 載入XML動畫

animator.setTarget(view);  
// 設置動畫對象

animator.start();  
// 啓動動畫
複製代碼

效果圖

效果圖

從上面能夠看出,ValueAnimator.ofInt()ValueAnimator.oFloat()僅僅只是在估值器上的區別:(即如何從初始值 過渡 到結束值)

  • ValueAnimator.oFloat()採用默認的浮點型估值器 (FloatEvaluator)
  • ValueAnimator.ofInt()採用默認的整型估值器(IntEvaluator

在使用上徹底沒有區別,此處對ValueAnimator.oFloat()的使用就不做過多描述。


5.1.3 ValueAnimator.ofObject()

  • 做用:**將初始值 以對象的形式 過渡到結束值 **

即經過操做 對象 實現動畫效果

  • 工做原理:

工做原理

  • 具體使用:
// 建立初始動畫時的對象  & 結束動畫時的對象
myObject object1 = new myObject();  
myObject object2 = new myObject();  

ValueAnimator anim = ValueAnimator.ofObject(new myObjectEvaluator(), object1, object2);  
// 建立動畫對象 & 設置參數
// 參數說明
// 參數1:自定義的估值器對象(TypeEvaluator 類型參數) - 下面會詳細介紹
// 參數2:初始動畫的對象
// 參數3:結束動畫的對象
anim.setDuration(5000);  
anim.start(); 
複製代碼

在繼續講解ValueAnimator.ofObject()的使用前,我先講一下估值器(TypeEvaluator

估值器(TypeEvaluator) 介紹

  • 做用:設置動畫 如何從初始值 過渡到 結束值 的邏輯
  1. 插值器(Interpolator)決定 值 的變化模式(勻速、加速blabla)
  2. 估值器(TypeEvaluator)決定 值 的具體變化數值

從5.1.2節可看到:

  • ValueAnimator.ofFloat()實現了 **將初始值 以浮點型的形式 過渡到結束值 ** 的邏輯,那麼這個過渡邏輯具體是怎麼樣的呢?
  • 實際上是系統內置了一個 FloatEvaluator估值器,內部實現了初始值與結束值 以浮點型的過渡邏輯
  • 咱們來看一下 FloatEvaluator的代碼實現:
public class FloatEvaluator implements TypeEvaluator {  
// FloatEvaluator實現了TypeEvaluator接口

// 重寫evaluate()
    public Object evaluate(float fraction, Object startValue, Object endValue) {  
// 參數說明
// fraction:表示動畫完成度(根據它來計算當前動畫的值)
// startValue、endValue:動畫的初始值和結束值
        float startFloat = ((Number) startValue).floatValue();  
        
        return startFloat + fraction * (((Number) endValue).floatValue() - startFloat);  
        // 初始值 過渡 到結束值 的算法是:
        // 1. 用結束值減去初始值,算出它們之間的差值
        // 2. 用上述差值乘以fraction係數
        // 3. 再加上初始值,就獲得當前動畫的值
    }  
}  
複製代碼
  • ValueAnimator.ofInt() & ValueAnimator.ofFloat()都具有系統內置的估值器,即FloatEvaluator & IntEvaluator

即系統已經默認實現了 如何從初始值 過渡到 結束值 的邏輯

  • 但對於ValueAnimator.ofObject(),從上面的工做原理能夠看出並無系統默認實現,由於對對象的動畫操做複雜 & 多樣,系統沒法知道如何從初始對象過分到結束對象
  • 所以,對於ValueAnimator.ofObject(),咱們需自定義估值器(TypeEvaluator)來告知系統如何進行從 初始對象 過渡到 結束對象的邏輯
  • 自定義實現的邏輯以下
// 實現TypeEvaluator接口
public class ObjectEvaluator implements TypeEvaluator{  

// 複寫evaluate()
// 在evaluate()裏寫入對象動畫過渡的邏輯
    @Override  
    public Object evaluate(float fraction, Object startValue, Object endValue) {  
        // 參數說明
        // fraction:表示動畫完成度(根據它來計算當前動畫的值)
        // startValue、endValue:動畫的初始值和結束值

        ... // 寫入對象動畫過渡的邏輯
        
        return value;  
        // 返回對象動畫過渡的邏輯計算後的值
    }  
  
複製代碼

實例說明

  • 下面我將用實例說明 該如何自定義TypeEvaluator接口並經過ValueAnimator.ofObject()實現動畫效果

  • 實現的動畫效果:一個圓從一個點 移動到 另一個點

    效果圖

  • 工程目錄文件以下:

    工程目錄

步驟1:定義對象類

  • 由於ValueAnimator.ofObject()是面向對象操做的,因此須要自定義對象類。
  • 本例須要操做的對象是 圓的點座標 Point.java
public class Point {

    // 設置兩個變量用於記錄座標的位置
    private float x;
    private float y;

    // 構造方法用於設置座標
    public Point(float x, float y) {
        this.x = x;
        this.y = y;
    }

    // get方法用於獲取座標
    public float getX() {
        return x;
    }

    public float getY() {
        return y;
    }
}
複製代碼

步驟2:根據需求實現TypeEvaluator接口

  • 實現TypeEvaluator接口的目的是自定義如何 從初始點座標 過渡 到結束點座標;
  • 本例實現的是一個從左上角到右下角的座標過渡邏輯。
    效果圖

PointEvaluator.java

// 實現TypeEvaluator接口
public class PointEvaluator implements TypeEvaluator {

    // 複寫evaluate()
    // 在evaluate()裏寫入對象動畫過渡的邏輯
    @Override
    public Object evaluate(float fraction, Object startValue, Object endValue) {

        // 將動畫初始值startValue 和 動畫結束值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());
        
        // 將計算後的座標封裝到一個新的Point對象中並返回
        Point point = new Point(x, y);
        return point;
    }

}
複製代碼
  • 上面步驟是根據需求自定義TypeEvaluator的實現
  • 下面將講解如何經過對 Point 對象進行動畫操做,從而實現整個自定義View的動畫效果。

步驟3:將屬性動畫做用到自定義View當中

MyView.java

/**
 * Created by Carson_Ho on 17/4/18.
 */
public class MyView extends View {
    // 設置須要用到的變量
    public static final float RADIUS = 70f;// 圓的半徑 = 70
    private Point currentPoint;// 當前點座標
    private Paint mPaint;// 繪圖畫筆
    

    // 構造方法(初始化畫筆)
    public MyView(Context context, AttributeSet attrs) {
        super(context, attrs);
        // 初始化畫筆
        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mPaint.setColor(Color.BLUE);
    }

    // 複寫onDraw()從而實現繪製邏輯
    // 繪製邏輯:先在初始點畫圓,經過監聽當前座標值(currentPoint)的變化,每次變化都調用onDraw()從新繪製圓,從而實現圓的平移動畫效果
    @Override
    protected void onDraw(Canvas canvas) {
        // 若是當前點座標爲空(即第一次)
        if (currentPoint == null) {
            currentPoint = new Point(RADIUS, RADIUS);
            // 建立一個點對象(座標是(70,70))

            // 在該點畫一個圓:圓心 = (70,70),半徑 = 70
            float x = currentPoint.getX();
            float y = currentPoint.getY();
            canvas.drawCircle(x, y, RADIUS, mPaint);


 // (重點關注)將屬性動畫做用到View中
            // 步驟1:建立初始動畫時的對象點  & 結束動畫時的對象點
            Point startPoint = new Point(RADIUS, RADIUS);// 初始點爲圓心(70,70)
            Point endPoint = new Point(700, 1000);// 結束點爲(700,1000)

            // 步驟2:建立動畫對象 & 設置初始值 和 結束值
            ValueAnimator anim = ValueAnimator.ofObject(new PointEvaluator(), startPoint, endPoint);
            // 參數說明
            // 參數1:TypeEvaluator 類型參數 - 使用自定義的PointEvaluator(實現了TypeEvaluator接口)
            // 參數2:初始動畫的對象點
            // 參數3:結束動畫的對象點

            // 步驟3:設置動畫參數
            anim.setDuration(5000);
            // 設置動畫時長

// 步驟3:經過 值 的更新監聽器,將改變的對象手動賦值給當前對象
// 此處是將 改變後的座標值對象 賦給 當前的座標值對象
            // 設置 值的更新監聽器
            // 即每當座標值(Point對象)更新一次,該方法就會被調用一次
            anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                @Override
                public void onAnimationUpdate(ValueAnimator animation) {
                    currentPoint = (Point) animation.getAnimatedValue();
                    // 將每次變化後的座標值(估值器PointEvaluator中evaluate()返回的Piont對象值)到當前座標值對象(currentPoint)
                    // 從而更新當前座標值(currentPoint)

// 步驟4:每次賦值後就從新繪製,從而實現動畫效果
                    invalidate();
                    // 調用invalidate()後,就會刷新View,即才能看到從新繪製的界面,即onDraw()會被從新調用一次
                    // 因此座標值每改變一次,就會調用onDraw()一次
                }
            });

            anim.start();
            // 啓動動畫


        } else {
            // 若是座標值不爲0,則畫圓
            // 因此座標值每改變一次,就會調用onDraw()一次,就會畫一次圓,從而實現動畫效果

            // 在該點畫一個圓:圓心 = (30,30),半徑 = 30
            float x = currentPoint.getX();
            float y = currentPoint.getY();
            canvas.drawCircle(x, y, RADIUS, mPaint);
        }
    }


}
複製代碼

步驟4:在佈局文件加入自定義View空間

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context="scut.carson_ho.valueanimator_ofobject.MainActivity">

    <scut.carson_ho.valueanimator_ofobject.MyView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
         />
</RelativeLayout>
複製代碼

步驟5:在主代碼文件設置顯示視圖

MainActivity.java

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }
}
複製代碼

效果圖

效果圖

源碼地址

Carson_Ho的Github地址:github.com/Carson-Ho/P…

特別注意

從上面能夠看出,其實ValueAnimator.ofObject()的本質仍是操做 ** 值 **,只是是採用將 多個值 封裝到一個對象裏的方式 同時對多個值一塊兒操做而已

就像上面的例子,本質仍是操做座標中的x,y兩個值,只是將其封裝到Point對象裏,方便同時操做x,y兩個值而已


  • 至此,關於屬性動畫中最核心的 ValueAnimator類已經講解完畢
  • 下面我將繼續講解另一個重要的類:ObjectAnimator

5.2 ObjectAnimator類

5.2.1 實現動畫的原理

直接對對象的屬性值進行改變操做,從而實現動畫效果

  1. 如直接改變 Viewalpha 屬性 從而實現透明度的動畫效果
  2. 繼承自ValueAnimator類,即底層的動畫實現機制是基於ValueAnimator
  • 本質原理: 經過不斷控制 值 的變化,再不斷 自動 賦給對象的屬性,從而實現動畫效果。以下圖:

工做原理

從上面的工做原理能夠看出:ObjectAnimatorValueAnimator類的區別:

  • ValueAnimator 類是先改變值,而後 手動賦值 給對象的屬性從而實現動畫;是 間接 對對象屬性進行操做;
  • ObjectAnimator 類是先改變值,而後 自動賦值 給對象的屬性從而實現動畫;是 直接 對對象屬性進行操做;

至因而如何自動賦值給對象的屬性,下面會詳細說明

5.2.2 具體使用

因爲是繼承了ValueAnimator類,因此使用的方法十分相似:XML 設置 / Java設置

設置方式1:Java 設置

ObjectAnimator animator = ObjectAnimator.ofFloat(Object object, String property, float ....values);  

// ofFloat()做用有兩個
// 1. 建立動畫實例
// 2. 參數設置:參數說明以下
// 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);
        // 設置動畫重複播放次數 = 重放次數+1
        // 動畫播放次數 = infinite時,動畫無限重複

        anim.setRepeatMode(ValueAnimator.RESTART);
        // 設置重複播放動畫模式
        // ValueAnimator.RESTART(默認):正序重放
        // ValueAnimator.REVERSE:倒序回放

animator.start();  
// 啓動動畫
複製代碼

**設置方法2:在XML 代碼中設置 **

  • 步驟1:在路徑 res/animator 的文件夾裏建立動畫效果.xml文件

此處設置爲res/animator/set_animation.xml

  • 步驟2:設置動畫參數

set_animation.xml

// ObjectAnimator 採用<animator>  標籤
<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"  
    android:valueFrom="1"   // 初始值
    android:valueTo="0"  // 結束值
    android:valueType="floatType"  // 變化值類型 :floatType & intType
    android:propertyName="alpha" // 對象變化的屬性名稱

/>  
複製代碼

在Java代碼中啓動動畫

Animator animator = AnimatorInflater.loadAnimator(context, R.animator.view_animation);  
// 載入XML動畫

animator.setTarget(view);  
// 設置動畫對象

animator.start();  
// 啓動動畫
複製代碼
  • 使用實例 此處先展現四種基本變換:平移、旋轉、縮放 & 透明度

a. 透明度

mButton = (Button) findViewById(R.id.Button);
        // 建立動畫做用對象:此處以Button爲例

        ObjectAnimator animator = ObjectAnimator.ofFloat(mButton, "alpha", 1f, 0f, 1f);
        // 表示的是:
        // 動畫做用對象是mButton
        // 動畫做用的對象的屬性是透明度alpha
        // 動畫效果是:常規 - 全透明 - 常規
        animator.setDuration(5000);
        animator.start();
複製代碼

屬性動畫 - 透明度.gif

b. 旋轉

mButton = (Button) findViewById(R.id.Button);
        // 建立動畫做用對象:此處以Button爲例

  ObjectAnimator animator = ObjectAnimator.ofFloat(mButton, "rotation", 0f, 360f);

        // 表示的是:
        // 動畫做用對象是mButton
        // 動畫做用的對象的屬性是旋轉alpha
        // 動畫效果是:0 - 360
        animator.setDuration(5000);
        animator.start();
複製代碼

屬性動畫- 旋轉.gif

c. 平移

mButton = (Button) findViewById(R.id.Button);
        // 建立動畫做用對象:此處以Button爲例

  float curTranslationX = mButton.getTranslationX();
        // 得到當前按鈕的位置
        ObjectAnimator animator = ObjectAnimator.ofFloat(mButton, "translationX", curTranslationX, 300,curTranslationX);
        

        // 表示的是:
        // 動畫做用對象是mButton
        // 動畫做用的對象的屬性是X軸平移(在Y軸上平移同理,採用屬性"translationY"
        // 動畫效果是:從當前位置平移到 x=1500 再平移到初始位置
        animator.setDuration(5000);
        animator.start();

複製代碼

屬性動畫 - X軸平移.gif

d. 縮放

mButton = (Button) findViewById(R.id.Button);
        // 建立動畫做用對象:此處以Button爲例

  ObjectAnimator animator = ObjectAnimator.ofFloat(mButton, "scaleX", 1f, 3f, 1f);
        // 表示的是:
        // 動畫做用對象是mButton
        // 動畫做用的對象的屬性是X軸縮放
        // 動畫效果是:放大到3倍,再縮小到初始大小
        animator.setDuration(5000);
        animator.start();
複製代碼

屬性動畫 - 縮放.gif


  • 在上面的講解,咱們使用了屬性動畫最基本的四種動畫效果:透明度、平移、旋轉 & 縮放

即在ObjectAnimator.ofFloat()的第二個參數String property傳入alpharotationtranslationXscaleY 等blabla

屬性 做用 數值類型
Alpha 控制View的透明度 float
TranslationX 控制X方向的位移 float
TranslationY 控制Y方向的位移 float
ScaleX 控制X方向的縮放倍數 float
ScaleY 控制Y方向的縮放倍數 float
Rotation 控制以屏幕方向爲軸的旋轉度數 float
RotationX 控制以X軸爲軸的旋轉度數 float
RotationY 控制以Y軸爲軸的旋轉度數 float

問題:那麼ofFloat()的第二個參數還能傳入什麼屬性值呢? 答案:任意屬性值。由於:

  • ObjectAnimator 類 對 對象屬性值 進行改變從而實現動畫效果的本質是:經過不斷控制 值 的變化,再不斷 自動 賦給對象的屬性,從而實現動畫效果

工做原理

  • 自動賦給對象的屬性的本質是調用該對象屬性的set() & get()方法進行賦值
  • 因此,ObjectAnimator.ofFloat(Object object, String property, float ....values)的第二個參數傳入值的做用是:讓ObjectAnimator類根據傳入的屬性名 去尋找 該對象對應屬性名的 set() & get()方法,從而進行對象屬性值的賦值,如上面的例子:
ObjectAnimator animator = ObjectAnimator.ofFloat(mButton, "rotation", 0f, 360f);
// 其實Button對象中並無rotation這個屬性值
// ObjectAnimator並非直接對咱們傳入的屬性名進行操做
// 而是根據傳入的屬性值"rotation" 去尋找對象對應屬性名對應的get和set方法,從而經過set() &  get()對屬性進行賦值

// 由於Button對象中有rotation屬性所對應的get & set方法
// 因此傳入的rotation屬性是有效的
// 因此才能對rotation這個屬性進行操做賦值
public void setRotation(float value);  
public float getRotation();  

// 實際上,這兩個方法是由View對象提供的,因此任何繼承自View的對象都具有這個屬性


複製代碼

至因而如何進行自動賦值的,咱們直接來看源碼分析:

// 使用方法
ObjectAnimator animator = ObjectAnimator.ofFloat(Object object, String property, float ....values);  
anim.setDuration(500);
animator.start();  
// 啓動動畫,源碼分析就直接從start()開始

<--  start()  -->
@Override  
public void start() {  
    AnimationHandler handler = sAnimationHandler.get();  

    if (handler != null) {  
        // 判斷等待動畫(Pending)中是否有和當前動畫相同的動畫,若是有就把相同的動畫給取消掉 
        numAnims = handler.mPendingAnimations.size();  
        for (int i = numAnims - 1; i >= 0; i--) {  
            if (handler.mPendingAnimations.get(i) instanceof ObjectAnimator) {  
                ObjectAnimator anim = (ObjectAnimator) handler.mPendingAnimations.get(i);  
                if (anim.mAutoCancel && hasSameTargetAndProperties(anim)) {  
                    anim.cancel();  
                }  
            }  
        }  
      // 判斷延遲動畫(Delay)中是否有和當前動畫相同的動畫,若是有就把相同的動畫給取消掉 
        numAnims = handler.mDelayedAnims.size();  
        for (int i = numAnims - 1; i >= 0; i--) {  
            if (handler.mDelayedAnims.get(i) instanceof ObjectAnimator) {  
                ObjectAnimator anim = (ObjectAnimator) handler.mDelayedAnims.get(i);  
                if (anim.mAutoCancel && hasSameTargetAndProperties(anim)) {  
                    anim.cancel();  
                }  
            }  
        }  
    }  
    
    super.start();  
   // 調用父類的start()
   // 由於ObjectAnimator類繼承ValueAnimator類,因此調用的是ValueAnimator的star()
   // 通過層層調用,最終會調用到 自動賦值給對象屬性值的方法
   // 下面就直接看該部分的方法
}  



<-- 自動賦值給對象屬性值的邏輯方法 ->>

// 步驟1:初始化動畫值
private void setupValue(Object target, Keyframe kf) {  
    if (mProperty != null) {  
        kf.setValue(mProperty.get(target));  
        // 初始化時,若是屬性的初始值沒有提供,則調用屬性的get()進行取值
    }  
        kf.setValue(mGetter.invoke(target));   
    }  
}  

// 步驟2:更新動畫值
// 當動畫下一幀來時(即動畫更新的時候),setAnimatedValue()都會被調用
void setAnimatedValue(Object target) {  
    if (mProperty != null) {  
        mProperty.set(target, getAnimatedValue());  
        // 內部調用對象該屬性的set()方法,從而從而將新的屬性值設置給對象屬性
    }  
    
}  
複製代碼

自動賦值的邏輯:

  1. 初始化時,若是屬性的初始值沒有提供,則調用屬性的 get()進行取值;
  2. 當 值 變化時,用對象該屬性的 set()方法,從而從而將新的屬性值設置給對象屬性。

因此:

  • ObjectAnimator 類針對的是任意對象 & 任意屬性值,並非單單針對於View對象

  • 若是須要採用ObjectAnimator 類實現動畫效果,那麼須要操做的對象就必須有該屬性的set() & get()

  • 同理,針對上述另外的三種基本動畫效果,View 也存在着setRotation()getRotation()setTranslationX()getTranslationX()setScaleY()getScaleY()set() & get()


5.2.3 經過自定義對象屬性實現動畫效果

對於屬性動畫,其拓展性在於:不侷限於系統限定的動畫,能夠自定義動畫,即自定義對象的屬性,並經過操做自定義的屬性從而實現動畫。

那麼,該如何自定義屬性呢?本質上,就是:

  • 爲對象設置須要操做屬性的set() & get()方法
  • 經過實現TypeEvaluator類從而定義屬性變化的邏輯

相似於ValueAnimator的過程

下面,我將用一個實例來講明如何經過自定義屬性實現動畫效果

  • 實現的動畫效果:一個圓的顏色漸變

    屬性動畫 - 顏色變化

  • 自定義屬性的邏輯以下:(須要自定義屬性爲圓的背景顏色)

自定義屬性的邏輯

步驟1:設置對象類屬性的set() & get()方法

設置對象類屬性的set() & get()有兩種方法:

  1. 經過繼承原始類,直接給類加上該屬性的 get()& set(),從而實現給對象加上該屬性的 get()& set()

  2. 經過包裝原始動畫對象,間接給對象加上該屬性的 get()& set()。即 用一個類來包裝原始對象

此處主要使用第一種方式進行展現。

關於第二種方式的使用,會在下一節進行詳細介紹。

MyView2.java

public class MyView2 extends View {
    // 設置須要用到的變量
    public static final float RADIUS = 100f;// 圓的半徑 = 100
    private Paint mPaint;// 繪圖畫筆

    private String color;
    // 設置背景顏色屬性

    // 設置背景顏色的get() & set()方法
    public String getColor() {
        return color;
    }

    public void setColor(String color) {
        this.color = color;
        mPaint.setColor(Color.parseColor(color));
        // 將畫筆的顏色設置成方法參數傳入的顏色
        invalidate();
        // 調用了invalidate()方法,即畫筆顏色每次改變都會刷新視圖,而後調用onDraw()方法從新繪製圓
        // 而由於每次調用onDraw()方法時畫筆的顏色都會改變,因此圓的顏色也會改變
    }


    // 構造方法(初始化畫筆)
    public MyView2(Context context, AttributeSet attrs) {
        super(context, attrs);
        // 初始化畫筆
        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mPaint.setColor(Color.BLUE);
    }

    // 複寫onDraw()從而實現繪製邏輯
    // 繪製邏輯:先在初始點畫圓,經過監聽當前座標值(currentPoint)的變化,每次變化都調用onDraw()從新繪製圓,從而實現圓的平移動畫效果
    @Override
    protected void onDraw(Canvas canvas) {
        canvas.drawCircle(500, 500, RADIUS, mPaint);
    }
}
複製代碼

步驟2:在佈局文件加入自定義View控件

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context="scut.carson_ho.valueanimator_ofobject.MainActivity">

    <scut.carson_ho.valueanimator_ofobject.MyView2
        android:id="@+id/MyView2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
         />
</RelativeLayout>
複製代碼

步驟3:根據需求實現TypeEvaluator接口

此處實現估值器的本質是:實現 顏色過渡的邏輯。

ColorEvaluator.java

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;
    }

}
複製代碼

步驟4:調用ObjectAnimator.ofObject()方法

MainActivity.java

public class MainActivity extends AppCompatActivity {

    MyView2 myView2;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        myView2 = (MyView2) findViewById(R.id.MyView2);
        ObjectAnimator anim = ObjectAnimator.ofObject(myView2, "color", new ColorEvaluator(),
                "#0000FF", "#FF0000");
        // 設置自定義View對象、背景顏色屬性值 & 顏色估值器
        // 本質邏輯:
        // 步驟1:根據顏色估值器不斷 改變 值 
        // 步驟2:調用set()設置背景顏色的屬性值(其實是經過畫筆進行顏色設置)
        // 步驟3:調用invalidate()刷新視圖,即調用onDraw()從新繪製,從而實現動畫效果

        anim.setDuration(8000);
        anim.start();
    }
}
複製代碼

效果圖

屬性動畫 - 顏色變化

源碼地址

Carson_Ho的Github地址


5.2.4 特別注意:如何手動設置對象類屬性的 set() & get()

a. 背景

  • ObjectAnimator 類 自動賦給對象的屬性 的本質是調用該對象屬性的set() & get()方法進行賦值
  • 因此,ObjectAnimator.ofFloat(Object object, String property, float ....values)的第二個參數傳入值的做用是:讓ObjectAnimator類根據傳入的屬性名 去尋找 該對象對應屬性名的 set() & get()方法,從而進行對象屬性值的賦值

從上面的原理可知,若是想讓對象的屬性a的動畫生效,屬性a須要同時知足下面兩個條件:

  1. 對象必需要提供屬性a的set()方法

a. 若是沒傳遞初始值,那麼須要提供get()方法,由於系統要去拿屬性a的初始值 b. 若該條件不知足,程序直接Crash

  1. 對象提供的 屬性a的set()方法 對 屬性a的改變 必須經過某種方法反映出來

a. 如帶來ui上的變化 b. 若這條不知足,動畫無效,但不會Crash)

上述條件,通常第二條都會知足,主要是在第一條

  1. 好比說:因爲ViewsetWidth()並非設置View的寬度,而是設置View的最大寬度和最小寬度的;因此經過setWidth()沒法改變控件的寬度;因此對View視圖的width作屬性動畫沒有效果
  2. 具體請看下面Button按鈕的例子
Button  mButton = (Button) findViewById(R.id.Button);
        // 建立動畫做用對象:此處以Button爲例
        // 此Button的寬高設置具體爲具體寬度200px

               ObjectAnimator.ofInt(mButton, "width", 500).setDuration(5000).start();
                 // 設置動畫的對象
複製代碼

效果圖

效果圖:不會有動畫效果

爲何沒有動畫效果呢?咱們來看ViewsetWidth方法

public void setWidth(int pixels) {  
    mMaxWidth = mMinWidth = pixels;  
    mMaxWidthMode = mMinWidthMode = PIXELS;  
    // 由於setWidth()並非設置View的寬度,而是設置Button的最大寬度和最小寬度的
    // 因此經過setWidth()沒法改變控件的寬度
   // 因此對width屬性作屬性動畫沒有效果
  
    requestLayout();  
    invalidate();  
}  
  

@ViewDebug.ExportedProperty(category = "layout")  
public final int getWidth() {  
    return mRight - mLeft;  
    // getWidth的確是獲取View的寬度
}  
複製代碼

b. 問題

那麼,針對上述對象屬性的set()不是設置屬性 或 根本沒有set() / get ()的狀況應該如何處理?

c. 解決方案

手動設置對象類屬性的set() & get()。共有兩種方法:

  1. 經過繼承原始類,直接給類加上該屬性的 get()& set(),從而實現給對象加上該屬性的 get()& set()

  2. 經過包裝原始動畫對象,間接給對象加上該屬性的 get()& set()。即 用一個類來包裝原始對象

對於第一種方法,在上面的例子已經說明;下面主要講解第二種方法:經過包裝原始動畫對象,間接給對象加上該屬性的get()& set()

本質上是採用了設計模式中的裝飾模式,即經過包裝類從而擴展對象的功能

仍是採用上述 Button 按鈕的例子

public class MainActivity extends AppCompatActivity {
    Button mButton;
    ViewWrapper wrapper;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mButton = (Button) findViewById(R.id.Button);
        // 建立動畫做用對象:此處以Button爲例

        wrapper = new ViewWrapper(mButton);
        // 建立包裝類,並傳入動畫做用的對象
        
        mButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {

                ObjectAnimator.ofInt(wrapper, "width", 500).setDuration(3000).start();
                // 設置動畫的對象是包裝類的對象
            }
        });

    }
    // 提供ViewWrapper類,用於包裝View對象
    // 本例:包裝Button對象
    private static class ViewWrapper {
        private View mTarget;

        // 構造方法:傳入須要包裝的對象
        public ViewWrapper(View target) {
            mTarget = target;
        }

        // 爲寬度設置get() & set()
        public int getWidth() {
            return mTarget.getLayoutParams().width;
        }

        public void setWidth(int width) {
            mTarget.getLayoutParams().width = width;
            mTarget.requestLayout();
        }

    }

}
複製代碼

效果圖

效果圖 - 動畫有效


5.4 總結

  • 對比ValueAnimator類 & ObjectAnimator 類,其實兩者都屬於屬性動畫,本質上都是一致的:先改變值,而後 賦值 給對象的屬性從而實現動畫效果。
  • 但兩者的區別在於: ValueAnimator 類是先改變值,而後 手動賦值 給對象的屬性從而實現動畫;是 間接 對對象屬性進行操做;

ValueAnimator 類本質上是一種 改變 值 的操做機制

ObjectAnimator類是先改變值,而後 自動賦值 給對象的屬性從而實現動畫;是 直接 對對象屬性進行操做;

能夠理解爲:ObjectAnimator更加智能、自動化程度更高


6. 額外的使用方法

6.1 組合動畫(AnimatorSet 類)

  • 單一動畫實現的效果至關有限,更多的使用場景是同時使用多種動畫效果,即組合動畫
  • 實現 組合動畫 的功能:AnimatorSet
  • 具體使用:
AnimatorSet.play(Animator anim)   :播放當前動畫
AnimatorSet.after(long delay)   :將現有動畫延遲x毫秒後執行
AnimatorSet.with(Animator anim)   :將現有動畫和傳入的動畫同時執行
AnimatorSet.after(Animator anim)   :將現有動畫插入到傳入的動畫以後執行
AnimatorSet.before(Animator anim) :  將現有動畫插入到傳入的動畫以前執行
複製代碼
  • 實例 主要動畫是平移,平移過程當中伴隨旋轉動畫,平移完後進行透明度變化

實現方式有 XML設置 / Java代碼設置

設置方式1:Java代碼設置

// 步驟1:設置須要組合的動畫效果
ObjectAnimator translation = ObjectAnimator.ofFloat(mButton, "translationX", curTranslationX, 300,curTranslationX);  
// 平移動畫
ObjectAnimator rotate = ObjectAnimator.ofFloat(mButton, "rotation", 0f, 360f);  
// 旋轉動畫
ObjectAnimator alpha = ObjectAnimator.ofFloat(mButton, "alpha", 1f, 0f, 1f);  
// 透明度動畫

// 步驟2:建立組合動畫的對象
AnimatorSet animSet = new AnimatorSet();  

// 步驟3:根據需求組合動畫
animSet.play(translation).with(rotate).before(alpha);  
animSet.setDuration(5000);  

// 步驟4:啓動動畫
animSet.start();  
複製代碼

效果圖

組合動畫.gif

設置方式2:XML設置

  • 步驟1:在 res/animator的文件夾裏建立動畫.xml文件

此處爲 res/animator/set_animation.xml

  • 步驟2:設置動畫效果

set_animation.xml

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
    android:ordering="sequentially" >
    // 表示Set集合內的動畫按順序進行
    // ordering的屬性值:sequentially & together
    // sequentially:表示set中的動畫,按照前後順序逐步進行(a 完成以後進行 b )
    // together:表示set中的動畫,在同一時間同時進行,爲默認值

    <set android:ordering="together" >
        // 下面的動畫同時進行
        <objectAnimator
            android:duration="2000"
            android:propertyName="translationX"
            android:valueFrom="0"
            android:valueTo="300"
            android:valueType="floatType" >
        </objectAnimator>
        
        <objectAnimator
            android:duration="3000"
            android:propertyName="rotation"
            android:valueFrom="0"
            android:valueTo="360"
            android:valueType="floatType" >
        </objectAnimator>
    </set>

        <set android:ordering="sequentially" >
            // 下面的動畫按序進行
            <objectAnimator
                android:duration="1500"
                android:propertyName="alpha"
                android:valueFrom="1"
                android:valueTo="0"
                android:valueType="floatType" >
            </objectAnimator>
            <objectAnimator
                android:duration="1500"
                android:propertyName="alpha"
                android:valueFrom="0"
                android:valueTo="1"
                android:valueType="floatType" >
            </objectAnimator>
        </set>

</set>
複製代碼

在Java代碼中啓動動畫

mButton = (Button) findViewById(R.id.Button);
        // 建立動畫做用對象:此處以Button爲例

        AnimatorSet animator = (AnimatorSet) AnimatorInflater.loadAnimator(this, R.animator.set_animation);
// 建立組合動畫對象  &  加載XML動畫
        animator.setTarget(mButton);
        // 設置動畫做用對象
        animator.start();
        // 啓動動畫
複製代碼

效果圖

同第一種方式相同。


6.2 ViewPropertyAnimator用法

  • 從上面能夠看出,屬性動畫的本質是對值操做
  • Java是面向對象的,因此 Google 團隊添加面向對象操做的屬性動畫使用 - ViewPropertyAnimator

可認爲是屬性動畫的一種簡寫方式

  • 具體使用
// 使用解析
        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()方法來啓動動畫
複製代碼

6.3 監聽動畫

  • Animation類經過監聽動畫開始 / 結束 / 重複 / 取消時刻來進行一系列操做,如跳轉頁面等等
  • 經過在Java代碼裏addListener()設置
Animation.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個方法都重寫。
複製代碼
  • Animator類、AnimatorSet類、ValueAnimatorObjectAnimator類存在如下繼承關係

各種繼承關係

  • 因此AnimatorSet類、ValueAnimatorObjectAnimator均可以使用addListener()監聽器進行動畫監聽

動畫適配器AnimatorListenerAdapter

  • 背景:有些時候咱們並不須要監聽動畫的全部時刻
  • 問題:但addListener(new AnimatorListener())監聽器是必須重寫4個時刻方法,這使得接口方法重寫太累贅
  • 解決方案:採用動畫適配器(AnimatorListenerAdapter),解決 實現接口繁瑣 的問題
anim.addListener(new AnimatorListenerAdapter() {  
// 向addListener()方法中傳入適配器對象AnimatorListenerAdapter()
// 因爲AnimatorListenerAdapter中已經實現好每一個接口
// 因此這裏不實現所有方法也不會報錯
    @Override  
    public void onAnimationStart(Animator animation) {  
    // 如想只想監聽動畫開始時刻,就只須要單獨重寫該方法就能夠
    }  
});  
複製代碼

至此,Android 動畫中的屬性動畫的全部知識點都講解完畢。


7. 總結

  • 屬性動畫的本質原理:經過不斷對值進行改變,並不斷將該值賦給對象的屬性,從而實現該對象在該屬性上的動畫效果;具體工做原理邏輯以下:
    工做原理
  • 屬性動畫的使用主要有如下類,具體以下:

主要使用類


請點贊!由於大家的贊同/鼓勵是我寫做的最大動力!


歡迎關注carson_ho的微信公衆號

示意圖

示意圖

相關文章
相關標籤/搜索