android開發筆記之屬性動畫

屬性動畫簡單介紹

作用對象:任意 Java 對象
不再侷限於 視圖View對象
實現的動畫效果:可自定義各種動畫效果
不再侷限於4種基本變換:平移、旋轉、縮放 & 透明度

特點
作用對象進行了擴展:不只是View對象,甚至沒對象也可以
動畫效果:不只是4種基本變換,還有其他動畫效果
作用領域:API11後引入的

工作原理:
在一定時間間隔內,通過不斷對值進行改變,並不斷將該值賦給對象的屬性,從而實現該對象在該屬性上的動畫效果

屬性動畫有兩個非常重要的類:

ValueAnimator 類 & ObjectAnimator 類

ObjectAnimator類

具體使用

設置方式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();  // 啓動動畫

四種基本Demo

四種基本變換:透明度,旋轉,平移,縮放

(1)透明度:

佈局文件activity_main.xml:

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">
    
    <Button
        android:id="@+id/button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello World 動畫!"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</android.support.constraint.ConstraintLayout>

實現代碼MainActivity.java:

import android.animation.ObjectAnimator;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;

public class MainActivity extends AppCompatActivity {

    private Button mButton;

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

    private void init() {
        mButton = (Button) findViewById(R.id.button);
        mButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                onDoAnimator();
            }
        });
    }

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

動畫效果:
在這裏插入圖片描述
(2)旋轉:

    private void onDoAnimator() {
        ObjectAnimator animator = ObjectAnimator.ofFloat(mButton, "rotation", 0f, 360f);
        // 動畫作用對象是mButton
        // 動畫作用的對象的屬性是旋轉alpha
        // 動畫效果是:0 - 360
        animator.setDuration(5000);
        animator.start();
    }

動畫效果:
在這裏插入圖片描述
(3)平移

private void onDoAnimator() {
    float curTranslationX = mButton.getTranslationX();
    // 獲得當前按鈕的位置
    ObjectAnimator animator = ObjectAnimator.ofFloat(mButton, "translationX", curTranslationX, 300,curTranslationX,-300,curTranslationX);
    // 動畫作用對象是mButton
    // 動畫作用的對象的屬性是X軸平移(在Y軸上平移同理,採用屬性"translationY"
    // 動畫效果是:從當前位置平移到 x=300 再平移到初始位置
    animator.setDuration(5000);
    animator.start();
}

動畫效果:
在這裏插入圖片描述

(4)縮放

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

動畫效果:
在這裏插入圖片描述
在ObjectAnimator.ofFloat()的第二個參數String property傳入alpha、rotation、translationX 和 scaleY 等:

屬性 作用 數值類型
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()方法,從而進行對象屬性值的賦值

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

對於屬性動畫,其拓展性在於:不侷限於系統限定的動畫,可以自定義動畫,即自定義對象的屬性,並通過操作自定義的屬性從而實現動畫。
那麼,該如何自定義屬性呢?本質上,就是:
爲對象設置需要操作屬性的set() & get()方法
通過實現TypeEvaluator類從而定義屬性變化的邏輯

實現的動畫效果:一個圓的顏色漸變
在這裏插入圖片描述
(1)定義MyView2 :

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.view.View;

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

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

(2)在佈局文件activity_main.xml中引用MyView2:

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <com.readygo.valueanimatordemo.MyView2
        android:id="@+id/myView2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />
        />

</android.support.constraint.ConstraintLayout>

(3)實現TypeEvaluator需求接口,實現 顏色過渡的邏輯

import android.animation.TypeEvaluator;

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

    private int mCurrentRed;
    private int mCurrentGreen ;
    private int mCurrentBlue ;

    // 在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()方法

import android.animation.ObjectAnimator;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;

public class MainActivity extends AppCompatActivity {

    private MyView2 myView2;

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

    private void init() {
        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();
    }

}

ValueAnimator類

具體使用

屬性動畫機制中最核心的一個類,實現動畫的原理:通過不斷控制值的變化,再不斷手動賦給對象的屬性,從而實現動畫效果.

ValueAnimator類中有3個重要方法:

ValueAnimator.ofInt(int values)
ValueAnimator.ofFloat(float values)
ValueAnimator.ofObject(int values)

ValueAnimator.ofInt(int values)

具體使用

作用:將初始值 以整型數值的形式 過渡到結束值
即估值器是整型估值器 - IntEvaluator

設置方式1: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

我將結合 手動賦值給對象屬性 這一步驟,從而實現一個完整的動畫效果。

public class MainActivity extends AppCompatActivity {

    private Button mButton;
    private ValueAnimator valueAnimator;

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

    private void init() {
        mButton = (Button) findViewById(R.id.button);
        mButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                onDoAnimator();
            }
        });

// 步驟1:設置屬性數值的初始值 & 結束值
        System.out.println(mButton.getLayoutParams().width);
        valueAnimator = ValueAnimator.ofInt(150, 500);
        // 初始值 150
        // 結束值 = 500
        // ValueAnimator.ofInt()內置了整型估值器,直接採用默認的.不需要設置
        // 即默認設置瞭如何從初始值150 過渡到 結束值500

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

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

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

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

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

            }
        });
    }

    private void onDoAnimator() {
        // 啓動動畫
        valueAnimator.start();
    }
}

動畫效果圖:
在這裏插入圖片描述

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="floatType"/>  

步驟3:在Java代碼中啓動動畫

Animator animator = AnimatorInflater.loadAnimator(context, R.animator.set_animation);  
// 載入XML動畫
animator.setTarget(view);  
// 設置動畫對象
animator.start();  
// 啓動動畫

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

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(); 

先講一下估值器(TypeEvaluator)

估值器(TypeEvaluator) 介紹

作用:設置動畫 如何從初始值 過渡到 結束值 的邏輯
插值器(Interpolator)決定 值 的變化模式(勻速、加速等)
估值器(TypeEvaluator)決定 值 的具體變化數值
我們來看一下 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. 再加上初始值,就得到當前動畫的值
    }  
}  

自定義實現的邏輯如下

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

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

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

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

步驟1:定義對象類

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

import android.animation.TypeEvaluator;

// 實現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;
    }

}

步驟3:將屬性動畫作用到自定義View當中
MyView.java

import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.view.View;

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:在佈局文件activity_main.xml 加入自定義View空間

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

        <com.readygo.valueanimatordemo.MyView
            android:id="@+id/myView"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toTopOf="parent" />
            />

</android.support.constraint.ConstraintLayout>

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

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;

public class MainActivity extends AppCompatActivity {

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

動畫效果:
在這裏插入圖片描述

ValueAnimator類 & ObjectAnimator 類 總結:

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

ObjectAnimator與 ValueAnimator類的區別:

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

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()方法來啓動動畫

監聽動畫

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個方法都重寫。

動畫適配器AnimatorListenerAdapter

背景:有些時候我們並不需要監聽動畫的所有時刻
問題:但addListener(new AnimatorListener())監聽器是必須重寫4個時刻方法,這使得接口方法重寫太累贅
解決方案:採用動畫適配器(AnimatorListenerAdapter),解決實現接口繁瑣 的問題

anim.addListener(new AnimatorListenerAdapter() {  
// 向addListener()方法中傳入適配器對象AnimatorListenerAdapter()
// 由於AnimatorListenerAdapter中已經實現好每個接口
// 所以這裏不實現全部方法也不會報錯
    @Override  
    public void onAnimationStart(Animator animation) {  
    // 如想只想監聽動畫開始時刻,就只需要單獨重寫該方法就可以
    }  
});  

參考資料

1.Android 屬性動畫:這是一篇很詳細的 屬性動畫 總結&攻略
https://www.jianshu.com/p/2412d00a0ce4
2.android開發筆記之animation(一)
https://blog.csdn.net/hfreeman2008/article/details/39138303
3.Android 屬性動畫:這是一篇很詳細的 屬性動畫 總結&攻略
https://www.jianshu.com/p/2412d00a0ce4

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