Android ObjectAnimator類:手把手帶你自定義屬性動畫

轉自 https://blog.csdn.net/carson_ho/article/details/99712272

  • 屬性動畫的使用 是 Android 開發中常用的知識
  • 今天,我將講解屬性動畫使用中最核心的一個方法類:ObjectAnimator,希望你們會喜歡。

  • 目錄

    示意圖


    儲備知識

    閱讀本文前,請先閱讀文章:Android:這是一份全面 & 詳細的動畫入門學習指南


    1. 簡介

    • 實現屬性動畫中的一個核心方法類
    • 繼承自ValueAnimator類,即底層的動畫實現機制是基於ValueAnimator類

    2. 實現動畫的原理

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

    如直接改變 Viewalpha 屬性 從而實現透明度的動畫效果


    3. 本質原理

    通過不斷控制 值 的變化,再不斷 自動 賦給對象的屬性,從而實現動畫效果。如下圖:

    示意圖

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

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


    4. 基礎使用

    由於是繼承了ValueAnimator類,所以使用的方法十分類似:XML 設置 / Java設置

    4.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();
    // 啓動動畫

    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    4.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" // 對象變化的屬性名稱
    

    />

    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    在Java代碼中啓動動畫

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

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

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

    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    4.3 使用實例

    此處先展示四種基本變換:平移、旋轉、縮放 & 透明度

    a. 透明度

    mButton = (Button) findViewById(R.id.Button);
            // 創建動畫作用對象:此處以Button爲例
    
        ObjectAnimator animator = ObjectAnimator.ofFloat(mButton, "alpha", 1f, 0f, 1f);
        // 表示的是:
        // 動畫作用對象是mButton
        // 動畫作用的對象的屬性是透明度alpha
        // 動畫效果是:常規 - 全透明 - 常規
        animator.setDuration(5000);
        animator.start();
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    屬性動畫 - 透明度.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();
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    屬性動畫- 旋轉.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();
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    屬性動畫 - 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();

    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    屬性動畫 - 縮放.gif


    4. 通過自定義對象屬性實現動畫效果

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

    即在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的對象都具備這個屬性

    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    4.1 原理解析

    至於是如何進行自動賦值的,我們直接來看源碼分析:

    // 使用方法
    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 &gt;= 0; i--) {  
            if (handler.mPendingAnimations.get(i) instanceof ObjectAnimator) {  
                ObjectAnimator anim = (ObjectAnimator) handler.mPendingAnimations.get(i);  
                if (anim.mAutoCancel &amp;&amp; hasSameTargetAndProperties(anim)) {  
                    anim.cancel();  
                }  
            }  
        }  
      // 判斷延遲動畫(Delay)中是否有和當前動畫相同的動畫,如果有就把相同的動畫給取消掉 
        numAnims = handler.mDelayedAnims.size();  
        for (int i = numAnims - 1; i &gt;= 0; i--) {  
            if (handler.mDelayedAnims.get(i) instanceof ObjectAnimator) {  
                ObjectAnimator anim = (ObjectAnimator) handler.mDelayedAnims.get(i);  
                if (anim.mAutoCancel &amp;&amp; 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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64

    自動賦值的邏輯:

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

    所以:

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

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

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


    4.2 具體使用

    對於屬性動畫,其拓展性在於:不侷限於系統限定的動畫,可以自定義動畫,即自定義對象的屬性,並通過操作自定義的屬性從而實現動畫。

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

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

    類似於ValueAnimator的過程

    4.3 實例講解

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

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

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

    自定義屬性的邏輯

    步驟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() &amp; 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);
    }
    

    }

    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38

    步驟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">
    
    &lt;scut.carson_ho.valueanimator_ofobject.MyView2
        android:id="@+id/MyView2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
         /&gt;
    

    </RelativeLayout>

    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    步驟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()決定如何根據差值來決定顏色變化的快慢 -&gt;&gt;關注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()-&gt;&gt;關注2
        // 最終將RGB顏色拼裝起來,並作爲最終的結果返回
        return currentColor;
    }
    
    
    // 關注1:getCurrentColor()
    // 具體是根據fraction值來計算當前的顏色。
    
    private int getCurrentColor(int startColor, int endColor, int colorDiff,
                                int offset, float fraction) {
        int currentColor;
        if (startColor &gt; endColor) {
            currentColor = (int) (startColor - (fraction * colorDiff - offset));
            if (currentColor &lt; endColor) {
                currentColor = endColor;
            }
        } else {
            currentColor = (int) (startColor + (fraction * colorDiff - offset));
            if (currentColor &gt; 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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92

    步驟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對象、背景顏色屬性值 &amp; 顏色估值器
        // 本質邏輯:
        // 步驟1:根據顏色估值器不斷 改變 值 
        // 步驟2:調用set()設置背景顏色的屬性值(實際上是通過畫筆進行顏色設置)
        // 步驟3:調用invalidate()刷新視圖,即調用onDraw()重新繪製,從而實現動畫效果
    
        anim.setDuration(8000);
        anim.start();
    }
    

    }

    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    效果圖

    屬性動畫 - 顏色變化

    源碼地址

    Carson_Ho的Github地址


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

    5.1 背景說明

    • 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();
                 // 設置動畫的對象
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    效果圖

    效果圖:不會有動畫效果

    爲什麼沒有動畫效果呢?我們來看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的寬度
    }

    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    5.2 問題

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

    5.3 解決方案

    手動設置對象類屬性的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() &amp; set()
        public int getWidth() {
            return mTarget.getLayoutParams().width;
        }
    
        public void setWidth(int width) {
            mTarget.getLayoutParams().width = width;
            mTarget.requestLayout();
        }
    
    }
    

    }

    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48

    效果圖

    效果圖 - 動畫有效


    6. 與ValueAnimator類對比

    對比於屬性動畫中另外一個比較核心的使用類:ValueAnimator類:

    6.1 相同點

    二者都屬於屬性動畫,本質上都是一致的:先改變值,然後 賦值 給對象的屬性從而實現動畫效果。

    6.2 不同點

    二者的區別在於:賦值的操作是手動 or 自動

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

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

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

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

    至此,關於屬性動畫中的核心使用類ObjectAnimator講解完畢


    7. 總結

    • 本文對Android 屬性動畫中的最核心的 ObjectAnimator類進行全面 & 詳細介紹
    • 接下來,我將繼續對Android的相關知識進行分析,感興趣的同學可以繼續關注本人的技術博客Carson_Ho的技術博客

    發表評論
    所有評論
    還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
    相關文章