動腦高級UI預(yù)習(xí)資料——屬性動畫

前言

  • 動畫的使用 是 Android 開發(fā)中常用的知識
  • 本文將詳細介紹 Android 動畫中 屬性動畫的原理 & 使用
動畫類型

目錄

目錄

1. 屬性動畫出現(xiàn)的原因

  • 屬性動畫(Property Animation)是在 Android 3.0API 11)后才提供的一種全新動畫模式
  • 那么為什么要提供屬性動畫(Property Animation)?

1.1 背景

實現(xiàn)動畫效果在Android開發(fā)中非常常見揭保,因此Android系統(tǒng)一開始就提供了兩種實現(xiàn)動畫的方式:

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

1.2 問題

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

a. 作用對象局限:View

即補間動畫 只能夠作用在視圖View上肥橙,即只可以對一個ButtonTextView秸侣、甚至是LinearLayout存筏、或者其它繼承自View的組件進行動畫操作,但無法對非View的對象進行動畫操作

  1. 有些情況下的動畫效果只是視圖的某個屬性 & 對象而不是整個視圖味榛;
  2. 如椭坚,現(xiàn)需要實現(xiàn)視圖的顏色動態(tài)變化,那么就需要操作視圖的顏色屬性從而實現(xiàn)動畫效果搏色,而不是針對整個視圖進行動畫操作
b. 沒有改變View的屬性善茎,只是改變視覺效果
  • 補間動畫只是改變了View的視覺效果,而不會真正去改變View的屬性频轿。
  • 如垂涯,將屏幕左上角的按鈕 通過補間動畫 移動到屏幕的右下角
  • 點擊當(dāng)前按鈕位置(屏幕右下角)是沒有效果的,因為實際上按鈕還是停留在屏幕左上角略吨,補間動畫只是將這個按鈕繪制到屏幕右下角集币,改變了視覺效果而已。
c. 動畫效果單一
  • 補間動畫只能實現(xiàn)平移翠忠、旋轉(zhuǎn)鞠苟、縮放 & 透明度這些簡單的動畫需求
  • 一旦遇到相對復(fù)雜的動畫效果,即超出了上述4種動畫效果秽之,那么補間動畫則無法實現(xiàn)当娱。

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

1.3 問題

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

2. 簡介

  • 作用對象:任意 Java 對象

不再局限于 視圖View對象

  • 實現(xiàn)的動畫效果:可自定義各種動畫效果

不再局限于4種基本變換:平移、旋轉(zhuǎn)河质、縮放 & 透明度


3. 特點

  • 作用對象進行了擴展:不只是View對象冀惭,甚至沒對象也可以
  • 動畫效果:不只是4種基本變換震叙,還有其他動畫效果
  • 作用領(lǐng)域:API11后引入的

4. 工作原理

  • 在一定時間間隔內(nèi),通過不斷對值進行改變散休,并不斷將該值賦給對象的屬性媒楼,從而實現(xiàn)該對象在該屬性上的動畫效果

可以是任意對象的任意屬性

  • 具體的工作原理邏輯如下:
工作原理
  • 從上述工作原理可以看出屬性動畫有兩個非常重要的類:ValueAnimator 類 & ObjectAnimator
  • 其實屬性動畫的使用基本都是依靠這兩個類
  • 所以,在下面介紹屬性動畫的具體使用時戚丸,我會著重介紹這兩個類划址。

5. 具體使用

5.1 ValueAnimator類

  • 定義:屬性動畫機制中 最核心的一個類
  • 實現(xiàn)動畫的原理:通過不斷控制 值 的變化,再不斷 手動 賦給對象的屬性限府,從而實現(xiàn)動畫效果夺颤。如圖下:
工作原理

從上面原理可以看出:ValueAnimator類中有3個重要方法:

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

下面我將逐一介紹。

5.1.1 ValueAnimator.ofInt(int values)

  • 作用:將初始值 以整型數(shù)值的形式 過渡到結(jié)束值

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

  • 工作原理:
工作原理
  • 具體使用:

特別說明:

  1. 因為ValueAnimator本質(zhì)只是一種值的操作機制胁勺,所以下面的介紹先是展示如何改變一個值的過程(下面的實例主要講解:如何將一個值從0平滑地過渡到3)
  2. 至于如何實現(xiàn)動畫世澜,是需要開發(fā)者手動將這些 值 賦給 對象的屬性值。關(guān)于這部分在下節(jié)會進行說明署穗。

操作值的方式 分為 XML 設(shè)置 / Java 代碼設(shè)置
設(shè)置方式1:Java代碼設(shè)置

實際開發(fā)中宜狐,建議使用Java代碼實現(xiàn)屬性動畫:因為很多時候?qū)傩缘钠鹗贾凳菬o法提前確定的(無法使用XML設(shè)置),這就需要在Java代碼里動態(tài)獲取蛇捌。

// 步驟1:設(shè)置動畫屬性的初始值 & 結(jié)束值
ValueAnimator anim = ValueAnimator.ofInt(0, 3);
        // ofInt()作用有兩個
        // 1\. 創(chuàng)建動畫實例
        // 2\. 將傳入的多個Int參數(shù)進行平滑過渡:此處傳入0和1,表示將值從0平滑過渡到1
        // 如果傳入了3個Int參數(shù) a,b,c ,則是先從a平滑過渡到b,再從b平滑過渡到C,以此類推
        // ValueAnimator.ofInt()內(nèi)置了整型估值器,直接采用默認的.不需要設(shè)置咱台,即默認設(shè)置了如何從初始值 過渡到 結(jié)束值
        // 關(guān)于自定義插值器我將在下節(jié)進行講解
        // 下面看看ofInt()的源碼分析 ->>關(guān)注1

// 步驟2:設(shè)置動畫的播放各種屬性
        anim.setDuration(500);
        // 設(shè)置動畫運行的時長

        anim.setStartDelay(500);
        // 設(shè)置動畫延遲播放時間

        anim.setRepeatCount(0);
        // 設(shè)置動畫重復(fù)播放次數(shù) = 重放次數(shù)+1
        // 動畫播放次數(shù) = infinite時,動畫無限重復(fù)

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

// 步驟3:將改變的值手動賦值給對象的屬性值:通過動畫的更新監(jiān)聽器
        // 設(shè)置 值的更新監(jiān)聽器
        // 即:值每次改變络拌、變化一次,該方法就會被調(diào)用一次
        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:刷新視圖春贸,即重新繪制,從而實現(xiàn)動畫效果
                View.requestLayout();

            }
        });

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

// 關(guān)注1:ofInt()源碼分析
    public static ValueAnimator ofInt(int... values) {
        // 允許傳入一個或多個Int參數(shù)
        // 1\. 輸入一個的情況(如a):從0過渡到a遗遵;
        // 2\. 輸入多個的情況(如a萍恕,b,c):先從a平滑過渡到b车要,再從b平滑過渡到C

        ValueAnimator anim = new ValueAnimator();
        // 創(chuàng)建動畫對象
        anim.setIntValues(values);
        // 將傳入的值賦值給動畫對象
        return anim;
    }

效果圖

值 從初始值 過度到 結(jié)束值 的過程如下:

效果圖

設(shè)置方法2:在XML 代碼中設(shè)置
具備重用性允粤,即將通用的動畫寫到XML里,可在各個界面中去重用它

  • 步驟1:在路徑 res/animator的文件夾里創(chuàng)建相應(yīng)的動畫 .xml文件

此處設(shè)置為res/animator/set_animation.xml

  • 步驟2:設(shè)置動畫參數(shù)

set_animation.xml

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

    android:duration="3000" // 動畫持續(xù)時間(ms)翼岁,必須設(shè)置类垫,動畫才有效果
    android:startOffset ="1000" // 動畫延遲開始時間(ms)
    android:fillBefore = “true” // 動畫播放完后,視圖是否會停留在動畫開始的狀態(tài)琅坡,默認為true
    android:fillAfter = “false” // 動畫播放完后悉患,視圖是否會停留在動畫結(jié)束的狀態(tài),優(yōu)先于fillBefore值榆俺,默認為false
    android:fillEnabled= “true” // 是否應(yīng)用fillBefore值售躁,對fillAfter值無影響坞淮,默認為true
    android:repeatMode= “restart” // 選擇重復(fù)播放動畫模式,restart代表正序重放陪捷,reverse代表倒序回放回窘,默認為restart|
    android:repeatCount = “0” // 重放次數(shù)(所以動畫的播放次數(shù)=重放次數(shù)+1),為infinite時無限重復(fù)
    android:interpolator = @[package:]anim/interpolator_resource // 插值器揩局,即影響動畫的播放速度,下面會詳細講

/>  

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

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

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

效果圖

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

實例說明

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

// 步驟1:設(shè)置屬性數(shù)值的初始值 & 結(jié)束值
        ValueAnimator valueAnimator = ValueAnimator.ofInt(mButton.getLayoutParams().width, 500);
        // 初始值 = 當(dāng)前按鈕的寬度付枫,此處在xml文件中設(shè)置為150
        // 結(jié)束值 = 500
        // ValueAnimator.ofInt()內(nèi)置了整型估值器,直接采用默認的.不需要設(shè)置
        // 即默認設(shè)置了如何從初始值150 過渡到 結(jié)束值500

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

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

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

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

// 步驟4:刷新視圖阐滩,即重新繪制,從而實現(xiàn)動畫效果
                mButton.requestLayout();

            }
        });

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

    }

效果圖

效果圖

5.1.2 ValueAnimator.oFloat(float values)

  • 作用:將初始值 以浮點型數(shù)值的形式 過渡到結(jié)束值

  • 工作原理:

工作原理
  • 具體使用:分為 XML 設(shè)置 / Java 代碼設(shè)置

設(shè)置方法1:在 Java 代碼中設(shè)置

ValueAnimator anim = ValueAnimator.ofFloat(0, 3);  
// 其他使用類似ValueAnimator.ofInt(int values)县忌,此處不作過多描述

設(shè)置方法2:在XML 代碼中設(shè)置

  • 步驟1:在路徑 res/animator的文件夾里創(chuàng)建相應(yīng)的動畫.xml文件

此處設(shè)置為res/animator/set_animation.xml

  • 步驟2:設(shè)置動畫參數(shù)

set_animation.xml

// ValueAnimator采用<animator>  標簽
<animator xmlns:android="http://schemas.android.com/apk/res/android"  
    // 設(shè)置屬性同上
    android:valueFrom="0"  
    android:valueTo="100"  
    android:valueType="intType"/>  

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

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

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

效果圖

效果圖

從上面可以看出掂榔,ValueAnimator.ofInt()ValueAnimator.oFloat()僅僅只是在估值器上的區(qū)別:(即如何從初始值 過渡 到結(jié)束值)

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

在使用上完全沒有區(qū)別,此處對ValueAnimator.oFloat()的使用就不作過多描述症杏。


5.1.3 ValueAnimator.ofObject()

  • 作用:將初始值 以對象的形式 過渡到結(jié)束值

即通過操作 對象 實現(xiàn)動畫效果

  • 工作原理:
工作原理
  • 具體使用:
// 創(chuàng)建初始動畫時的對象  & 結(jié)束動畫時的對象
myObject object1 = new myObject();  
myObject object2 = new myObject();  

ValueAnimator anim = ValueAnimator.ofObject(new myObjectEvaluator(), object1, object2);  
// 創(chuàng)建動畫對象 & 設(shè)置參數(shù)
// 參數(shù)說明
// 參數(shù)1:自定義的估值器對象(TypeEvaluator 類型參數(shù)) - 下面會詳細介紹
// 參數(shù)2:初始動畫的對象
// 參數(shù)3:結(jié)束動畫的對象
anim.setDuration(5000);  
anim.start(); 

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

估值器(TypeEvaluator) 介紹

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

從5.1.2節(jié)可看到:

  • ValueAnimator.ofFloat()實現(xiàn)了 **將初始值 以浮點型的形式 過渡到結(jié)束值 ** 的邏輯厉颤,那么這個過渡邏輯具體是怎么樣的呢穴豫?
  • 其實是系統(tǒng)內(nèi)置了一個 FloatEvaluator估值器,內(nèi)部實現(xiàn)了初始值與結(jié)束值 以浮點型的過渡邏輯
  • 我們來看一下 FloatEvaluator的代碼實現(xiàn):
public class FloatEvaluator implements TypeEvaluator {  
// FloatEvaluator實現(xiàn)了TypeEvaluator接口

// 重寫evaluate()
    public Object evaluate(float fraction, Object startValue, Object endValue) {  
// 參數(shù)說明
// fraction:表示動畫完成度(根據(jù)它來計算當(dāng)前動畫的值)
// startValue逼友、endValue:動畫的初始值和結(jié)束值
        float startFloat = ((Number) startValue).floatValue();  

        return startFloat + fraction * (((Number) endValue).floatValue() - startFloat);  
        // 初始值 過渡 到結(jié)束值 的算法是:
        // 1\. 用結(jié)束值減去初始值精肃,算出它們之間的差值
        // 2\. 用上述差值乘以fraction系數(shù)
        // 3\. 再加上初始值,就得到當(dāng)前動畫的值
    }  
}  

  • ValueAnimator.ofInt() & ValueAnimator.ofFloat()都具備系統(tǒng)內(nèi)置的估值器帜乞,即FloatEvaluator & IntEvaluator

即系統(tǒng)已經(jīng)默認實現(xiàn)了 如何從初始值 過渡到 結(jié)束值 的邏輯

  • 但對于ValueAnimator.ofObject()司抱,從上面的工作原理可以看出并沒有系統(tǒng)默認實現(xiàn),因為對對象的動畫操作復(fù)雜 & 多樣黎烈,系統(tǒng)無法知道如何從初始對象過度到結(jié)束對象
  • 因此习柠,對于ValueAnimator.ofObject(),我們需自定義估值器(TypeEvaluator)來告知系統(tǒng)如何進行從 初始對象 過渡到 結(jié)束對象的邏輯
  • 自定義實現(xiàn)的邏輯如下
// 實現(xiàn)TypeEvaluator接口
public class ObjectEvaluator implements TypeEvaluator{  

// 復(fù)寫evaluate()
// 在evaluate()里寫入對象動畫過渡的邏輯
    @Override  
    public Object evaluate(float fraction, Object startValue, Object endValue) {  
        // 參數(shù)說明
        // fraction:表示動畫完成度(根據(jù)它來計算當(dāng)前動畫的值)
        // startValue怨喘、endValue:動畫的初始值和結(jié)束值

        ... // 寫入對象動畫過渡的邏輯

        return value;  
        // 返回對象動畫過渡的邏輯計算后的值
    }  

實例說明

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

  • 實現(xiàn)的動畫效果:一個圓從一個點 移動到 另外一個點

效果圖
  • 工程目錄文件如下:
目錄工程

步驟1:定義對象類

  • 因為ValueAnimator.ofObject()是面向?qū)ο蟛僮鞯慕蚧孕枰远x對象類。
  • 本例需要操作的對象是 圓的點坐標
    Point.java
public class Point {

    // 設(shè)置兩個變量用于記錄坐標的位置
    private float x;
    private float y;

    // 構(gòu)造方法用于設(shè)置坐標
    public Point(float x, float y) {
        this.x = x;
        this.y = y;
    }

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

    public float getY() {
        return y;
    }
}

步驟2:根據(jù)需求實現(xiàn)TypeEvaluator接口

  • 實現(xiàn)TypeEvaluator接口的目的是自定義如何 從初始點坐標 過渡 到結(jié)束點坐標必怜;
  • 本例實現(xiàn)的是一個從左上角到右下角的坐標過渡邏輯肉拓。
效果圖

PointEvaluator.java

// 實現(xiàn)TypeEvaluator接口
public class PointEvaluator implements TypeEvaluator {

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

        // 將動畫初始值startValue 和 動畫結(jié)束值endValue 強制類型轉(zhuǎn)換成Point對象
        Point startPoint = (Point) startValue;
        Point endPoint = (Point) endValue;

        // 根據(jù)fraction來計算當(dāng)前動畫的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;
    }

}

  • 上面步驟是根據(jù)需求自定義TypeEvaluator的實現(xiàn)
  • 下面將講解如何通過對 Point 對象進行動畫操作,從而實現(xiàn)整個自定義View的動畫效果梳庆。

步驟3:將屬性動畫作用到自定義View當(dāng)中

MyView.java

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

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

    // 復(fù)寫onDraw()從而實現(xiàn)繪制邏輯
    // 繪制邏輯:先在初始點畫圓,通過監(jiān)聽當(dāng)前坐標值(currentPoint)的變化,每次變化都調(diào)用onDraw()重新繪制圓,從而實現(xiàn)圓的平移動畫效果
    @Override
    protected void onDraw(Canvas canvas) {
        // 如果當(dāng)前點坐標為空(即第一次)
        if (currentPoint == null) {
            currentPoint = new Point(RADIUS, RADIUS);
            // 創(chuàng)建一個點對象(坐標是(70,70))

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

 // (重點關(guān)注)將屬性動畫作用到View中
            // 步驟1:創(chuàng)建初始動畫時的對象點  & 結(jié)束動畫時的對象點
            Point startPoint = new Point(RADIUS, RADIUS);// 初始點為圓心(70,70)
            Point endPoint = new Point(700, 1000);// 結(jié)束點為(700,1000)

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

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

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

// 步驟4:每次賦值后就重新繪制卑惜,從而實現(xiàn)動畫效果
                    invalidate();
                    // 調(diào)用invalidate()后,就會刷新View,即才能看到重新繪制的界面,即onDraw()會被重新調(diào)用一次
                    // 所以坐標值每改變一次,就會調(diào)用onDraw()一次
                }
            });

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

        } else {
            // 如果坐標值不為0,則畫圓
            // 所以坐標值每改變一次,就會調(diào)用onDraw()一次,就會畫一次圓,從而實現(xiàn)動畫效果

            // 在該點畫一個圓:圓心 = (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:在主代碼文件設(shè)置顯示視圖

MainActivity.java

public class MainActivity extends AppCompatActivity {

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

效果圖

效果圖

源碼地址

Carson_Ho的Github地址:https://github.com/Carson-Ho/PropertyAnimator_ofObject

特別注意

從上面可以看出,其實ValueAnimator.ofObject()的本質(zhì)還是操作 ** 值 **驻售,只是是采用將 多個值 封裝到一個對象里的方式 同時對多個值一起操作而已

就像上面的例子露久,本質(zhì)還是操作坐標中的x,y兩個值欺栗,只是將其封裝到Point對象里毫痕,方便同時操作x,y兩個值而已


  • 至此迟几,關(guān)于屬性動畫中最核心的 ValueAnimator類已經(jīng)講解完畢
  • 下面我將繼續(xù)講解另外一個重要的類:ObjectAnimator

5.2 ObjectAnimator類

5.2.1 實現(xiàn)動畫的原理

直接對對象的屬性值進行改變操作消请,從而實現(xiàn)動畫效果

  1. 如直接改變 Viewalpha 屬性 從而實現(xiàn)透明度的動畫效果
  2. 繼承自ValueAnimator類,即底層的動畫實現(xiàn)機制是基于ValueAnimator
  • 本質(zhì)原理: 通過不斷控制 值 的變化类腮,再不斷 自動 賦給對象的屬性臊泰,從而實現(xiàn)動畫效果。如下圖:
工作原理

從上面的工作原理可以看出:ObjectAnimatorValueAnimator類的區(qū)別:

  • ValueAnimator 類是先改變值蚜枢,然后 手動賦值 給對象的屬性從而實現(xiàn)動畫缸逃;是 間接 對對象屬性進行操作;
  • ObjectAnimator 類是先改變值厂抽,然后 自動賦值 給對象的屬性從而實現(xiàn)動畫需频;是 直接 對對象屬性進行操作;

至于是如何自動賦值給對象的屬性筷凤,下面會詳細說明

5.2.2 具體使用

由于是繼承了ValueAnimator類贺辰,所以使用的方法十分類似:XML 設(shè)置 / Java設(shè)置

設(shè)置方式1:Java 設(shè)置

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

// ofFloat()作用有兩個
// 1\. 創(chuàng)建動畫實例
// 2\. 參數(shù)設(shè)置:參數(shù)說明如下
// Object object:需要操作的對象
// String property:需要操作的對象的屬性
// float ....values:動畫初始值 & 結(jié)束值(不固定長度)
// 若是兩個參數(shù)a,b,則動畫效果則是從屬性的a值到b值
// 若是三個參數(shù)a,b,c嵌施,則則動畫效果則是從屬性的a值到b值再到c值
// 以此類推
// 至于如何從初始值 過渡到 結(jié)束值,同樣是由估值器決定莽鸭,此處ObjectAnimator.ofFloat()是有系統(tǒng)內(nèi)置的浮點型估值器FloatEvaluator吗伤,同ValueAnimator講解

anim.setDuration(500);
        // 設(shè)置動畫運行的時長

        anim.setStartDelay(500);
        // 設(shè)置動畫延遲播放時間

        anim.setRepeatCount(0);
        // 設(shè)置動畫重復(fù)播放次數(shù) = 重放次數(shù)+1
        // 動畫播放次數(shù) = infinite時,動畫無限重復(fù)

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

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

設(shè)置方法2:在XML 代碼中設(shè)置

  • 步驟1:在路徑 res/animator 的文件夾里創(chuàng)建動畫效果.xml文件

此處設(shè)置為res/animator/set_animation.xml

  • 步驟2:設(shè)置動畫參數(shù)

set_animation.xml

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

/>  

在Java代碼中啟動動畫

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

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

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

  • 使用實例
    此處先展示四種基本變換:平移、旋轉(zhuǎn)硫眨、縮放 & 透明度

a. 透明度

mButton = (Button) findViewById(R.id.Button);
        // 創(chuàng)建動畫作用對象:此處以Button為例

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

屬性動畫透明度.gif

b. 旋轉(zhuǎn)

mButton = (Button) findViewById(R.id.Button);
        // 創(chuàng)建動畫作用對象:此處以Button為例

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

        // 表示的是:
        // 動畫作用對象是mButton
        // 動畫作用的對象的屬性是旋轉(zhuǎn)alpha
        // 動畫效果是:0 - 360
        animator.setDuration(5000);
        animator.start();

屬性動畫旋轉(zhuǎn).gif

c. 平移

mButton = (Button) findViewById(R.id.Button);
        // 創(chuàng)建動畫作用對象:此處以Button為例

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

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

屬性動畫X軸平移.gif

d. 縮放

mButton = (Button) findViewById(R.id.Button);
        // 創(chuàng)建動畫作用對象:此處以Button為例

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

屬性動畫縮放.gif

  • 在上面的講解,我們使用了屬性動畫最基本的四種動畫效果:透明度礁阁、平移巧号、旋轉(zhuǎn) & 縮放

即在ObjectAnimator.ofFloat()的第二個參數(shù)String property傳入alpharotation姥闭、translationXscaleY 等blabla

屬性 作用 數(shù)值類型
Alpha 控制View的透明度 float
TranslationX 控制X方向的位移 float
TranslationY 控制Y方向的位移 float
ScaleX 控制X方向的縮放倍數(shù) float
ScaleY 控制Y方向的縮放倍數(shù) float
Rotation 控制以屏幕方向為軸的旋轉(zhuǎn)度數(shù) float
RotationX 控制以X軸為軸的旋轉(zhuǎn)度數(shù) float
RotationY 控制以Y軸為軸的旋轉(zhuǎn)度數(shù) float

問題:那么ofFloat()的第二個參數(shù)還能傳入什么屬性值呢丹鸿?
答案:任意屬性值。因為:

  • ObjectAnimator 類 對 對象屬性值 進行改變從而實現(xiàn)動畫效果的本質(zhì)是:通過不斷控制 值 的變化棚品,再不斷 自動 賦給對象的屬性靠欢,從而實現(xiàn)動畫效果
工作原理
  • 自動賦給對象的屬性的本質(zhì)是調(diào)用該對象屬性的set() & get()方法進行賦值
  • 所以廊敌,ObjectAnimator.ofFloat(Object object, String property, float ....values)的第二個參數(shù)傳入值的作用是:讓ObjectAnimator類根據(jù)傳入的屬性名 去尋找 該對象對應(yīng)屬性名的 set() & get()方法,從而進行對象屬性值的賦值门怪,如上面的例子:
ObjectAnimator animator = ObjectAnimator.ofFloat(mButton, "rotation", 0f, 360f);
// 其實Button對象中并沒有rotation這個屬性值
// ObjectAnimator并不是直接對我們傳入的屬性名進行操作
// 而是根據(jù)傳入的屬性值"rotation" 去尋找對象對應(yīng)屬性名對應(yīng)的get和set方法骡澈,從而通過set() &  get()對屬性進行賦值

// 因為Button對象中有rotation屬性所對應(yīng)的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)中是否有和當(dāng)前動畫相同的動畫坦弟,如果有就把相同的動畫給取消掉 
        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)中是否有和當(dāng)前動畫相同的動畫护锤,如果有就把相同的動畫給取消掉 
        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();  
   // 調(diào)用父類的start()
   // 因為ObjectAnimator類繼承ValueAnimator類,所以調(diào)用的是ValueAnimator的star()
   // 經(jīng)過層層調(diào)用减拭,最終會調(diào)用到 自動賦值給對象屬性值的方法
   // 下面就直接看該部分的方法
}  

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

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

// 步驟2:更新動畫值
// 當(dāng)動畫下一幀來時(即動畫更新的時候)拧粪,setAnimatedValue()都會被調(diào)用
void setAnimatedValue(Object target) {  
    if (mProperty != null) {  
        mProperty.set(target, getAnimatedValue());  
        // 內(nèi)部調(diào)用對象該屬性的set()方法修陡,從而從而將新的屬性值設(shè)置給對象屬性
    }  

}  

自動賦值的邏輯:

  1. 初始化時,如果屬性的初始值沒有提供可霎,則調(diào)用屬性的 get()進行取值魄鸦;
  2. 當(dāng) 值 變化時,用對象該屬性的 set()方法正卧,從而從而將新的屬性值設(shè)置給對象屬性签孔。

所以:

  • ObjectAnimator 類針對的是任意對象 & 任意屬性值,并不是單單針對于View對象

  • 如果需要采用ObjectAnimator 類實現(xiàn)動畫效果壁熄,那么需要操作的對象就必須有該屬性的set() & get()

  • 同理,針對上述另外的三種基本動畫效果烛亦,View 也存在著setRotation()getRotation()丹诀、setTranslationX()getTranslationX()颁虐、setScaleY()椭员、getScaleY()set() & get()


5.2.3 通過自定義對象屬性實現(xiàn)動畫效果

對于屬性動畫笛园,其拓展性在于:不局限于系統(tǒng)限定的動畫隘击,可以自定義動畫,即自定義對象的屬性研铆,并通過操作自定義的屬性從而實現(xiàn)動畫埋同。

那么,該如何自定義屬性呢棵红?本質(zhì)上凶赁,就是:

  • 為對象設(shè)置需要操作屬性的set() & get()方法
  • 通過實現(xiàn)TypeEvaluator類從而定義屬性變化的邏輯

類似于ValueAnimator的過程

下面,我將用一個實例來說明如何通過自定義屬性實現(xiàn)動畫效果

  • 實現(xiàn)的動畫效果:一個圓的顏色漸變
屬性動畫顏色變化.gif
  • 自定義屬性的邏輯如下:(需要自定義屬性為圓的背景顏色)
自定義屬性的邏輯

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

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

  1. 通過繼承原始類逆甜,直接給類加上該屬性的 get()& set()虱肄,從而實現(xiàn)給對象加上該屬性的 get()& set()

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

此處主要使用第一種方式進行展示浩峡。

關(guān)于第二種方式的使用,會在下一節(jié)進行詳細介紹错敢。

MyView2.java

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

    private String color;
    // 設(shè)置背景顏色屬性

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

    public void setColor(String color) {
        this.color = color;
        mPaint.setColor(Color.parseColor(color));
        // 將畫筆的顏色設(shè)置成方法參數(shù)傳入的顏色
        invalidate();
        // 調(diào)用了invalidate()方法,即畫筆顏色每次改變都會刷新視圖翰灾,然后調(diào)用onDraw()方法重新繪制圓
        // 而因為每次調(diào)用onDraw()方法時畫筆的顏色都會改變,所以圓的顏色也會改變
    }

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

    // 復(fù)寫onDraw()從而實現(xiàn)繪制邏輯
    // 繪制邏輯:先在初始點畫圓,通過監(jiān)聽當(dāng)前坐標值(currentPoint)的變化,每次變化都調(diào)用onDraw()重新繪制圓,從而實現(xiàn)圓的平移動畫效果
    @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:根據(jù)需求實現(xiàn)TypeEvaluator接口

此處實現(xiàn)估值器的本質(zhì)是:實現(xiàn) 顏色過渡的邏輯。

ColorEvaluator.java

public class ColorEvaluator implements TypeEvaluator {
    // 實現(xiàn)TypeEvaluator接口

    private int mCurrentRed;

    private int mCurrentGreen ;

    private int mCurrentBlue ;

    // 復(fù)寫evaluate()
    // 在evaluate()里寫入對象動畫過渡的邏輯:此處是寫顏色過渡的邏輯
    @Override
    public Object evaluate(float fraction, Object startValue, Object endValue) {

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

        // 通過字符串截取的方式將初始化顏色分為RGB三個部分稚茅,并將RGB的值轉(zhuǎn)換成十進制數(shù)字
        // 那么每個顏色的取值范圍就是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);

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

        // 計算初始顏色和結(jié)束顏色之間的差值
        // 該差值決定著顏色變化的快慢:初始顏色值和結(jié)束顏色值很相近纸淮,那么顏色變化就會比較緩慢;否則,變化則很快
        // 具體如何根據(jù)差值來決定顏色變化快慢的邏輯寫在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()決定如何根據(jù)差值來決定顏色變化的快慢 ->>關(guān)注1
        } else if (mCurrentGreen != endGreen) {
            mCurrentGreen = getCurrentColor(startGreen, endGreen, colorDiff,
                    redDiff, fraction);
        } else if (mCurrentBlue != endBlue) {
            mCurrentBlue = getCurrentColor(startBlue, endBlue, colorDiff,
                    redDiff + greenDiff, fraction);
        }
        // 將計算出的當(dāng)前顏色的值組裝返回
        String currentColor = "#" + getHexString(mCurrentRed)
                + getHexString(mCurrentGreen) + getHexString(mCurrentBlue);

        // 由于我們計算出的顏色是十進制數(shù)字,所以需要轉(zhuǎn)換成十六進制字符串:調(diào)用getHexString()->>關(guān)注2
        // 最終將RGB顏色拼裝起來,并作為最終的結(jié)果返回
        return currentColor;
    }

    // 關(guān)注1:getCurrentColor()
    // 具體是根據(jù)fraction值來計算當(dāng)前的顏色亚享。

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

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

}

步驟4:調(diào)用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");
        // 設(shè)置自定義View對象、背景顏色屬性值 & 顏色估值器
        // 本質(zhì)邏輯:
        // 步驟1:根據(jù)顏色估值器不斷 改變 值 
        // 步驟2:調(diào)用set()設(shè)置背景顏色的屬性值(實際上是通過畫筆進行顏色設(shè)置)
        // 步驟3:調(diào)用invalidate()刷新視圖欺税,即調(diào)用onDraw()重新繪制侈沪,從而實現(xiàn)動畫效果

        anim.setDuration(8000);
        anim.start();
    }
}

效果圖

屬性動畫顏色變化.gif

源碼地址

Carson_Ho的Github地址


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

a. 背景

  • ObjectAnimator 類 自動賦給對象的屬性 的本質(zhì)是調(diào)用該對象屬性的set() & get()方法進行賦值
  • 所以,ObjectAnimator.ofFloat(Object object, String property, float ....values)的第二個參數(shù)傳入值的作用是:讓ObjectAnimator類根據(jù)傳入的屬性名 去尋找 該對象對應(yīng)屬性名的 set() & get()方法晚凿,從而進行對象屬性值的賦值

從上面的原理可知亭罪,如果想讓對象的屬性a的動畫生效,屬性a需要同時滿足下面兩個條件:

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

a. 如果沒傳遞初始值歼秽,那么需要提供get()方法应役,因為系統(tǒng)要去拿屬性a的初始值
b. 若該條件不滿足,程序直接Crash

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

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

上述條件院崇,一般第二條都會滿足,主要是在第一條

  1. 比如說:由于ViewsetWidth()并不是設(shè)置View的寬度袍祖,而是設(shè)置View的最大寬度和最小寬度的底瓣;所以通過setWidth()無法改變控件的寬度;所以對View視圖的width做屬性動畫沒有效果
  2. 具體請看下面Button按鈕的例子
       Button  mButton = (Button) findViewById(R.id.Button);
        // 創(chuàng)建動畫作用對象:此處以Button為例
        // 此Button的寬高設(shè)置具體為具體寬度200px

               ObjectAnimator.ofInt(mButton, "width", 500).setDuration(5000).start();
                 // 設(shè)置動畫的對象

效果圖

效果圖:不會有動畫效果.gif

為什么沒有動畫效果呢盲泛?我們來看ViewsetWidth方法

public void setWidth(int pixels) {  
    mMaxWidth = mMinWidth = pixels;  
    mMaxWidthMode = mMinWidthMode = PIXELS;  
    // 因為setWidth()并不是設(shè)置View的寬度濒持,而是設(shè)置Button的最大寬度和最小寬度的
    // 所以通過setWidth()無法改變控件的寬度
   // 所以對width屬性做屬性動畫沒有效果

    requestLayout();  
    invalidate();  
}  

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

b. 問題

那么,針對上述對象屬性的set()不是設(shè)置屬性 或 根本沒有set() / get ()的情況應(yīng)該如何處理寺滚?

c. 解決方案

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

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

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

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

本質(zhì)上是采用了設(shè)計模式中的裝飾模式杠氢,即通過包裝類從而擴展對象的功能

還是采用上述 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);
        // 創(chuàng)建動畫作用對象:此處以Button為例

        wrapper = new ViewWrapper(mButton);
        // 創(chuàng)建包裝類,并傳入動畫作用的對象

        mButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {

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

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

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

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

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

    }

}

效果圖

效果圖站刑,動畫有效.gif

5.4 總結(jié)

  • 對比ValueAnimator類 & ObjectAnimator 類,其實二者都屬于屬性動畫鼻百,本質(zhì)上都是一致的:先改變值绞旅,然后 賦值 給對象的屬性從而實現(xiàn)動畫效果。
  • 但二者的區(qū)別在于:
    ValueAnimator 類是先改變值温艇,然后 手動賦值 給對象的屬性從而實現(xiàn)動畫因悲;是 間接 對對象屬性進行操作;

ValueAnimator 類本質(zhì)上是一種 改變 值 的操作機制

ObjectAnimator類是先改變值勺爱,然后 自動賦值 給對象的屬性從而實現(xiàn)動畫晃琳;是 直接 對對象屬性進行操作;

可以理解為:ObjectAnimator更加智能琐鲁、自動化程度更高


6. 額外的使用方法

6.1 組合動畫(AnimatorSet 類)

  • 單一動畫實現(xiàn)的效果相當(dāng)有限卫旱,更多的使用場景是同時使用多種動畫效果,即組合動畫
  • 實現(xiàn) 組合動畫 的功能:AnimatorSet
  • 具體使用:
AnimatorSet.play(Animator anim)   :播放當(dāng)前動畫
AnimatorSet.after(long delay)   :將現(xiàn)有動畫延遲x毫秒后執(zhí)行
AnimatorSet.with(Animator anim)   :將現(xiàn)有動畫和傳入的動畫同時執(zhí)行
AnimatorSet.after(Animator anim)   :將現(xiàn)有動畫插入到傳入的動畫之后執(zhí)行
AnimatorSet.before(Animator anim) :  將現(xiàn)有動畫插入到傳入的動畫之前執(zhí)行

  • 實例
    主要動畫是平移围段,平移過程中伴隨旋轉(zhuǎn)動畫顾翼,平移完后進行透明度變化

實現(xiàn)方式有 XML設(shè)置 / Java代碼設(shè)置

設(shè)置方式1:Java代碼設(shè)置

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

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

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

// 步驟4:啟動動畫
animSet.start();  

效果圖

組合動畫.gif

設(shè)置方式2:XML設(shè)置

  • 步驟1:在 res/animator的文件夾里創(chuàng)建動畫.xml文件

此處為 res/animator/set_animation.xml

  • 步驟2:設(shè)置動畫效果

set_animation.xml

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
    android:ordering="sequentially" >
    // 表示Set集合內(nèi)的動畫按順序進行
    // 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);
        // 創(chuàng)建動畫作用對象:此處以Button為例

        AnimatorSet animator = (AnimatorSet) AnimatorInflater.loadAnimator(this, R.animator.set_animation);
// 創(chuàng)建組合動畫對象  &  加載XML動畫
        animator.setTarget(mButton);
        // 設(shè)置動畫作用對象
        animator.start();
        // 啟動動畫

效果圖

同第一種方式相同。


6.2 ViewPropertyAnimator用法

  • 從上面可以看出,屬性動畫的本質(zhì)是對值操作
  • Java是面向?qū)ο蟮亩文ィ?Google 團隊添加面向?qū)ο蟛僮鞯膶傩詣赢嬍褂?- ViewPropertyAnimator

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

  • 具體使用
// 使用解析
        View.animate().xxx().xxx();
        // ViewPropertyAnimator的功能建立在animate()上
        // 調(diào)用animate()方法返回值是一個ViewPropertyAnimator對象,之后的調(diào)用的所有方法都是通過該實例完成
        // 調(diào)用該實例的各種方法來實現(xiàn)動畫效果
        // ViewPropertyAnimator所有接口方法都使用連綴語法來設(shè)計取逾,每個方法的返回值都是它自身的實例
        // 因此調(diào)用完一個方法后可直接連綴調(diào)用另一方法,即可通過一行代碼就完成所有動畫效果

// 以下是例子
        mButton = (Button) findViewById(R.id.Button);
        // 創(chuàng)建動畫作用對象:此處以Button為例

        mButton.animate().alpha(0f);
        // 單個動畫設(shè)置:將按鈕變成透明狀態(tài) 
        mButton.animate().alpha(0f).setDuration(5000).setInterpolator(new BounceInterpolator());
        // 單個動畫效果設(shè)置 & 參數(shù)設(shè)置 
        mButton.animate().alpha(0f).x(500).y(500);
        // 組合動畫:將按鈕變成透明狀態(tài)再移動到(500,500)處

        // 特別注意:
        // 動畫自動啟動,無需調(diào)用start()方法.因為新的接口中使用了隱式啟動動畫的功能,只要我們將動畫定義完成后苹支,動畫就會自動啟動
        // 該機制對于組合動畫也同樣有效砾隅,只要不斷地連綴新的方法,那么動畫就不會立刻執(zhí)行债蜜,等到所有在ViewPropertyAnimator上設(shè)置的方法都執(zhí)行完畢后晴埂,動畫就會自動啟動
        // 如果不想使用這一默認機制,也可以顯式地調(diào)用start()方法來啟動動畫


6.3 監(jiān)聽動畫

  • Animation類通過監(jiān)聽動畫開始 / 結(jié)束 / 重復(fù) / 取消時刻來進行一系列操作寻定,如跳轉(zhuǎn)頁面等等
  • 通過在Java代碼里addListener()設(shè)置
      Animation.addListener(new AnimatorListener() {
          @Override
          public void onAnimationStart(Animation animation) {
              //動畫開始時執(zhí)行
          }

           @Override
          public void onAnimationRepeat(Animation animation) {
              //動畫重復(fù)時執(zhí)行
          }

         @Override
          public void onAnimationCancel()(Animation animation) {
              //動畫取消時執(zhí)行
          }

          @Override
          public void onAnimationEnd(Animation animation) {
              //動畫結(jié)束時執(zhí)行
          }
      });

// 特別注意:每次監(jiān)聽必須4個方法都重寫儒洛。

  • Animator類、AnimatorSet類狼速、ValueAnimator琅锻、ObjectAnimator類存在以下繼承關(guān)系
各類繼承關(guān)系
  • 所以AnimatorSet類、ValueAnimator向胡、ObjectAnimator都可以使用addListener()監(jiān)聽器進行動畫監(jiān)聽

動畫適配器AnimatorListenerAdapter

  • 背景:有些時候我們并不需要監(jiān)聽動畫的所有時刻
  • 問題:但addListener(new AnimatorListener())監(jiān)聽器是必須重寫4個時刻方法恼蓬,這使得接口方法重寫太累贅
  • 解決方案:采用動畫適配器(AnimatorListenerAdapter),解決
    實現(xiàn)接口繁瑣 的問題
anim.addListener(new AnimatorListenerAdapter() {  
// 向addListener()方法中傳入適配器對象AnimatorListenerAdapter()
// 由于AnimatorListenerAdapter中已經(jīng)實現(xiàn)好每個接口
// 所以這里不實現(xiàn)全部方法也不會報錯
    @Override  
    public void onAnimationStart(Animator animation) {  
    // 如想只想監(jiān)聽動畫開始時刻僵芹,就只需要單獨重寫該方法就可以
    }  
});  

至此处硬,Android 動畫中的屬性動畫的所有知識點都講解完畢。


7. 總結(jié)

  • 屬性動畫的本質(zhì)原理:通過不斷對值進行改變拇派,并不斷將該值賦給對象的屬性荷辕,從而實現(xiàn)該對象在該屬性上的動畫效果;具體工作原理邏輯如下:
工作原理
  • 屬性動畫的使用主要有以下類攀痊,具體如下:
主要使用類

原文作者:Carson_Ho
原文鏈接:http://www.reibang.com/p/2412d00a0ce4
來源:簡書

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末桐腌,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子苟径,更是在濱河造成了極大的恐慌案站,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,482評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件棘街,死亡現(xiàn)場離奇詭異蟆盐,居然都是意外死亡,警方通過查閱死者的電腦和手機遭殉,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,377評論 2 382
  • 文/潘曉璐 我一進店門石挂,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人险污,你說我怎么就攤上這事痹愚「辉溃” “怎么了?”我有些...
    開封第一講書人閱讀 152,762評論 0 342
  • 文/不壞的土叔 我叫張陵拯腮,是天一觀的道長窖式。 經(jīng)常有香客問我,道長动壤,這世上最難降的妖魔是什么萝喘? 我笑而不...
    開封第一講書人閱讀 55,273評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮琼懊,結(jié)果婚禮上阁簸,老公的妹妹穿的比我還像新娘。我一直安慰自己哼丈,他們只是感情好启妹,可當(dāng)我...
    茶點故事閱讀 64,289評論 5 373
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著削祈,像睡著了一般翅溺。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上髓抑,一...
    開封第一講書人閱讀 49,046評論 1 285
  • 那天咙崎,我揣著相機與錄音,去河邊找鬼吨拍。 笑死褪猛,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的羹饰。 我是一名探鬼主播伊滋,決...
    沈念sama閱讀 38,351評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼队秩!你這毒婦竟也來了笑旺?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,988評論 0 259
  • 序言:老撾萬榮一對情侶失蹤馍资,失蹤者是張志新(化名)和其女友劉穎筒主,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體鸟蟹,經(jīng)...
    沈念sama閱讀 43,476評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡乌妙,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,948評論 2 324
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了建钥。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片藤韵。...
    茶點故事閱讀 38,064評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖熊经,靈堂內(nèi)的尸體忽然破棺而出泽艘,到底是詐尸還是另有隱情欲险,我是刑警寧澤,帶...
    沈念sama閱讀 33,712評論 4 323
  • 正文 年R本政府宣布匹涮,位于F島的核電站盯荤,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏焕盟。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,261評論 3 307
  • 文/蒙蒙 一宏粤、第九天 我趴在偏房一處隱蔽的房頂上張望脚翘。 院中可真熱鬧,春花似錦绍哎、人聲如沸来农。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,264評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽沃于。三九已至,卻和暖如春海诲,著一層夾襖步出監(jiān)牢的瞬間繁莹,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,486評論 1 262
  • 我被黑心中介騙來泰國打工特幔, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留咨演,地道東北人。 一個月前我還...
    沈念sama閱讀 45,511評論 2 354
  • 正文 我出身青樓蚯斯,卻偏偏與公主長得像薄风,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子拍嵌,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,802評論 2 345

推薦閱讀更多精彩內(nèi)容