Android Animation:這一次讓你完全瞭解 Android Property Animation

在正式開始講解 Property Animation 以前,先放一張用 Property Animation 實現的效果圖,有興趣的小夥伴能夠先自行嘗試下:android

1. 屬性動畫概述

1.1 概念

在一段時間內經過修改對象的屬性而造成的動畫叫屬性動畫。算法

Creates an animation by modifying an object's property values over a set period of time with an Animator.bash

1.2 屬性動畫的做用是什麼?

從上面的定義可知,屬性動畫的主要是修改對象的屬性,如 View 的背景顏色、透明值、位置等。app

1.3 爲何 Google 官方會在 Android 3.0 的時候添加 Property Animation?

不是已經有 Tween Animation 了嗎,爲何還會有 Property Animation?換句話說,Property Animation 到底能幹哪些 Tween Animation 不能幹的活?想明白了這些問題,天然就明白了爲何 Google 官方會在 Android 3.0 添加 Property Animation。ide

Tween Animation 存在的問題:函數

序號 內容
1 Tween Animation 只能做用於 View 的屬性,不能做用於普通 Object 的屬性
2 Tween Animation 只能改變 View 的一部分屬性,如 View 的 BackgroundColor 它就不能改變
3 Tween Animation 只能改變 View 的「表面」,不能改變 View 的實際屬性

簡單解釋下上面表格中列舉的內容:佈局

  1. Tween Animation 只能做用於 View,不能做用於普通 Object 的屬性

Tween Animation 只能做用於繪製在屏幕上的 View,而不能做用於普通的 Object,以下面的 Student 類,Tween Animation 就不能做用於它的屬性:post

public class Student {

    private String name;
    private int age;
    private int studentNumber;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public int getStudentNumber() {
        return studentNumber;
    }

    public void setStudentNumber(int studentNumber) {
        this.studentNumber = studentNumber;
    }
}
複製代碼
  1. Tween Animation 只能改變 View 的一部分屬性,如 View 的 BackgroundColor 它就不能改變

Tween Animation 包括五類動畫,分別是:動畫

序號 類名
1 AlphaAnimation
2 ScaleAnimation
3 TranslateAnimation
4 RotateAnimation
5 AnimationSet

也就是說,Tween Animation 只支持修改 View 的這幾個方面:Alpha、Scale、Translate、Rotate 和這些的組合,一旦想要改變的 View 的屬性不在這個範圍內,Tween Animation 就無能爲力了,如 View 的 BackgroundColor。ui

  1. Tween Animation 只能改變 View 的「表面」,不能改變 View 的實際屬性

經過 Tween Animation 修改 View 的位置,改變的只是 View 繪製的界面,而 View 實際的位置並未改變:

上圖中,爲 ImageView 和 ImageView 所在的父容器都添加了點擊事件。當點擊 ImageView 父容器的時候,執行 TranslateAnimation 移動 ImageView 的位置。當點擊 ImageView 的時候,彈出 Toast 提示當前日期。由上面的執行結果可知:當執行完 TranslateAnimation 以後,點擊 ImageView 執行完動畫以後最終所在的位置並未提示彈出 Toast,反而點擊 ImageView 執行 TranslateAnimation 動畫以前所在的位置,彈出了 Toast。

Property Animation 的出現完美地解決了以上問題。

1.4. 屬性動畫繼承結構

屬性動畫涉及的類主要有:

序號 類名 做用
1 Animator 全部 Animator 的父類,主要用於定義通用的接口
2 AnimatorSet 主要用於組合多個屬性動畫
3 ValueAnimator 屬性動畫的一種,主要用於根據起始值和終止值產生動畫,只負責產生在起始值和終止值之間的值,
不負責更新界面,須要用戶本身實現更新界面的邏輯
4 ObjectAnimator 屬性動畫的一種,主要用於根據起始值和終止值產生動畫,並將動畫產生的值設置在目標對象上
5 TimeAnimator 不經常使用,在此不作介紹,想了解的小夥伴,請自行查閱相關文檔

繼承結構以下:

2. 如何定義屬性動畫?

建立屬性動畫的方式有兩種:

  1. XML
  2. CODE

2.1 經過 XML 建立屬性動畫

2.1.1 經過 XML 建立 ValueAnimator
2.1.1.1 語法
<animator
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="int"
    android:interpolator="@[package:]anim/interpolator_resource"
    android:valueType=["intType" | "floatType"]
    android:valueFrom="float | int | color"
    android:valueTo="float | int | color"
    android:startOffset="int"
    android:repeatCount="int"
    android:repeatMode=["repeat" | "reverse"]
    />
複製代碼
2.1.1.2 屬性詳解
屬性 含義 取值範圍
xmlns:android 聲明 XML 佈局文件屬性命名空間 schemas.android.com/apk/res/and…
android:duration 動畫的執行時間 必須大於等於 0,不然程序將報錯
android:interpolator 插值器,決定了動畫的變化率 Android,Custom
android:valueType 動畫值的類型 整型,浮點型,當屬性動畫值的類型爲顏色值時能夠省略
android:valueFrom 動畫起始值 浮點數,整型數或者顏色值。當爲顏色值時,必須符合顏色的定義方式(# + 六位十六進制數),不然程序將報錯
android:valueTo 動畫結束值 浮點數,整型數或者顏色值。當爲顏色值時,必須符合顏色的定義方式(# + 六位十六進制數),不然程序將報錯
android:startOffset 動畫開始偏移時間 整型數,默認爲 0。當爲負數時,效果和默認值同樣
android:repeatCount 動畫重複的次數 整型數字,默認爲 0。當爲負數時,表示無限循環
android:repeatMode 當動畫的執行次數大於 1 時,下一次動畫執行的方式 從新開始(默認),反着執行
2.1.1.3 示例

與 Tween Animation 不一樣,Property Animation 建立的 Animation 在 res/animator 文件夾下。

//1. 建立 value_animator.xml
<?xml version="1.0" encoding="utf-8"?>
<animator xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="1800"
    android:interpolator="@android:anim/accelerate_decelerate_interpolator"
    android:valueType="floatType"
    android:valueFrom="-100"
    android:valueTo="800"
    android:startOffset="0"
    android:repeatCount="infinite"
    android:repeatMode="reverse"
    />
    
//2. 在代碼中使用 value_animator
ValueAnimator mValueAnimator = (ValueAnimator) AnimatorInflater.loadAnimator(this, R.animator.value_animator);
mValueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
    @Override
    public void onAnimationUpdate(ValueAnimator animation) {
        mTarget.setY((Float) animation.getAnimatedValue());
    }
});
mValueAnimator.start();
複製代碼

最終效果以下:

2.1.2 經過 XML 建立 ObjectAnimator
2.1.2.1 語法
<objectAnimator
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="int"
    android:interpolator="@[package:]anim/interpolator_resource"
    android:propertyName="string"
    android:valueType=["intType" | "floatType"]
    android:valueFrom="float | int | color"
    android:valueTo="float | int | color"
    android:startOffset="int"
    android:repeatCount="int"
    android:repeatMode=["repeat" | "reverse"]
    />
複製代碼
2.1.2.2 屬性詳解
屬性 含義 取值範圍
xmlns:android 聲明 XML 佈局文件屬性命名空間 schemas.android.com/apk/res/and…
android:duration 動畫的執行時間 必須大於等於 0,不然程序將報錯
android:interpolator 插值器,決定了動畫的變化率 Android,Custom
android:propertyName 動畫目標對象要改變的屬性 字符串
android:valueType 動畫值的類型 整型,浮點型,當屬性動畫值的類型爲顏色值時能夠省略
android:valueFrom 動畫起始值 浮點數,整型數或者顏色值。當爲顏色值時,必須符合顏色的定義方式(# + 六位十六進制數),不然程序將報錯
android:valueTo 動畫結束值 浮點數,整型數或者顏色值。當爲顏色值時,必須符合顏色的定義方式(# + 六位十六進制數),不然程序將報錯
android:startOffset 動畫開始偏移時間 整型數,默認爲 0。當爲負數時,效果和默認值同樣
android:repeatCount 動畫重複的次數 整型數字,默認爲 0。當爲負數時,表示無限循環
android:repeatMode 當動畫的執行次數大於 1 時,下一次動畫執行的方式 從新開始(默認),反着執行
2.1.2.3 示例
//1. 建立 object_animator.xml
<?xml version="1.0" encoding="utf-8"?>
<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="1800"
    android:interpolator="@android:anim/accelerate_decelerate_interpolator"
    android:propertyName="Y"
    android:valueType="floatType"
    android:valueFrom="0"
    android:valueTo="800"
    android:startOffset="0"
    android:repeatCount="infinite"
    android:repeatMode="reverse"
    />
    
//2. 在代碼中使用 object_animator
ObjectAnimator mObjectAnimator = (ObjectAnimator) AnimatorInflater.loadAnimator(this, R.animator.object_animator);
mObjectAnimator.setTarget(mTarget);
mObjectAnimator.start();
複製代碼

最終效果以下:

2.1.3 經過 XML 建立 AnimatorSet
2.1.3.1 語法
<set
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:ordering=["together" | "sequentially"]>

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

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

    <set>
        ...
    </set>
</set>
複製代碼
2.1.3.2 屬性詳解
屬性 含義 取值範圍
xmlns:android 聲明 XML 佈局文件屬性命名空間 schemas.android.com/apk/res/and…
android:ordering 多個動畫的執行順序 together,多個動畫同時執行;sequentially,多個動畫按照聲明的順序執行
2.1.3.3 示例
//1. 建立 animator_set.xml
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
    android:ordering="together"
    >
    <objectAnimator
        android:duration="@integer/integer_one_thousand_and_eight_hundred"
        android:interpolator="@android:anim/accelerate_decelerate_interpolator"
        android:propertyName="Y"
        android:valueType="floatType"
        android:valueFrom="0"
        android:valueTo="800"
        android:startOffset="0"
        android:repeatCount="infinite"
        android:repeatMode="reverse"
        />
    <objectAnimator
        android:duration="@integer/integer_one_thousand_and_eight_hundred"
        android:interpolator="@android:anim/accelerate_decelerate_interpolator"
        android:propertyName="ScaleX"
        android:valueType="floatType"
        android:valueFrom="1"
        android:valueTo="2"
        android:startOffset="0"
        android:repeatCount="infinite"
        android:repeatMode="reverse"
        />
    <objectAnimator
        android:duration="@integer/integer_one_thousand_and_eight_hundred"
        android:interpolator="@android:anim/accelerate_decelerate_interpolator"
        android:propertyName="ScaleY"
        android:valueType="floatType"
        android:valueFrom="1"
        android:valueTo="2"
        android:startOffset="0"
        android:repeatCount="infinite"
        android:repeatMode="reverse"
        />
</set>

//2. 在代碼中使用 animator_set
AnimatorSet mAnimatorSet = (AnimatorSet)AnimatorInflater.loadAnimator(this, R.animator.animator_set);
mAnimatorSet.setTarget(mTarget);
mAnimatorSet.start();
複製代碼

最終效果以下:

2.2 經過 CODE 建立屬性動畫

在 Android 中,大多數狀況下,能經過 XML 實現的功能幾乎也能經過代碼實現,接下來,讓咱們看下如何經過代碼實現上面的動畫。

2.2.1 經過 CODE 建立 ValueAnimator
2.2.1.1 語法
ValueAnimator valueAnimator = ValueAnimator ofFloat(float... values);
valueAnimator.setDuration(long duration);
valueAnimator.setInterpolator(TimeInterpolator value);
valueAnimator.addUpdateListener(AnimatorUpdateListener listener);
…
valueAnimator.start();
複製代碼
2.2.1.2 示例
ValueAnimator mValueAnimator = ValueAnimator.ofFloat(0, 800);
mValueAnimator.setDuration(1800);
mValueAnimator.setInterpolator(new AccelerateDecelerateInterpolator());
mValueAnimator.setRepeatCount(ValueAnimator.INFINITE);
mValueAnimator.setRepeatMode(ValueAnimator.REVERSE);
mValueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
    @Override
    public void onAnimationUpdate(ValueAnimator animation) {
        mTarget.setY((Float) animation.getAnimatedValue());
    }
});
mValueAnimator.start();
複製代碼

最終效果以下:

2.2.2 經過 CODE 建立 ObjectAnimator
2.2.2.1 語法
ObjectAnimator objectAnimator = ObjectAnimator.ofObject(Object target, String propertyName, TypeEvaluator evaluator, Object... values);
objectAnimator.setDuration(long duration);
objectAnimator.setInterpolator(TimeInterpolator value);
…
objectAnimator.start();
複製代碼
2.2.2.2 示例
ObjectAnimator mObjectAnimator = ObjectAnimator.ofFloat(mTarget, "y", 0, 800);
mObjectAnimator.setDuration(1800);
mObjectAnimator.setInterpolator(new AccelerateDecelerateInterpolator());
mObjectAnimator.setRepeatCount(ValueAnimator.INFINITE);
mObjectAnimator.setRepeatMode(ValueAnimator.REVERSE);
mObjectAnimator.start();
複製代碼

最終效果以下:

2.2.3 經過 CODE 建立 AnimatorSet
2.2.3.1 語法
AnimatorSet animatorSet = new AnimatorSet();
animatorSet.playTogether(Animator... items);
animatorSet.playSequentially(Animator... items);
//非必須
animatorSet.setTarget(mTarget);
…
animatorSet.start();
複製代碼
2.2.3.2 示例
ObjectAnimator translateYObjectAnimator = ObjectAnimator.ofFloat(mTarget, "y", 0, 800);
translateYObjectAnimator.setDuration(1800);
translateYObjectAnimator.setInterpolator(new AccelerateDecelerateInterpolator());
translateYObjectAnimator.setRepeatCount(ValueAnimator.INFINITE);
translateYObjectAnimator.setRepeatMode(ValueAnimator.REVERSE);
ObjectAnimator scaleXObjectAnimator = ObjectAnimator.ofFloat(mTarget, "scaleX", 1, 2);
scaleXObjectAnimator.setDuration(1800);
scaleXObjectAnimator.setInterpolator(new AccelerateDecelerateInterpolator());
scaleXObjectAnimator.setRepeatCount(ValueAnimator.INFINITE);
scaleXObjectAnimator.setRepeatMode(ValueAnimator.REVERSE);
ObjectAnimator scaleYObjectAnimator = ObjectAnimator.ofFloat(mTarget, "scaleY", 1, 2);
scaleYObjectAnimator.setDuration(1800);
scaleYObjectAnimator.setInterpolator(new AccelerateDecelerateInterpolator());
scaleYObjectAnimator.setRepeatCount(ValueAnimator.INFINITE);
scaleYObjectAnimator.setRepeatMode(ValueAnimator.REVERSE);
mAnimatorSet = new AnimatorSet();
mAnimatorSet.playTogether(translateYObjectAnimator, scaleXObjectAnimator, scaleYObjectAnimator);
mAnimatorSet.playSequentially();
//非必須
//        mAnimatorSet.setTarget(mTarget);
mAnimatorSet.start();
複製代碼

最終效果以下:

2.3 監聽屬性動畫

Property Animation 中一共有三種監聽事件:

  1. AnimatorListener;
  2. AnimatorPauseListener;
  3. AnimatorUpdateListener;
2.3.1 AnimatorListener

AnimatorListener 接口主要用於監聽 Property Animation 的開始、結束、取消、重複狀態,須要實現的方法分別是:

@Override
public void onAnimationStart(Animator animation) {}

@Override
public void onAnimationEnd(Animator animation) {}

@Override
public void onAnimationCancel(Animator animation) {}

@Override
public void onAnimationRepeat(Animator animation) {}
複製代碼
2.3.2 AnimatorPauseListener

AnimatorPauseListener 主要用於監聽 Property Animation 的暫停、恢復狀態,須要實現的方法分別是:

@Override
public void onAnimationPause(Animator animation) {}

@Override
public void onAnimationResume(Animator animation) {}
複製代碼
2.3.3 AnimatorUpdateListener

AnimatorUpdateListener 是 ValueAnimator 及其子類特有的接口,主要用於監聽動畫中值的變化,用於手動更新界面,須要實現的方法是:

@Override
public void onAnimationUpdate(ValueAnimator animation) {}
複製代碼

接下來用實例演示下這些方法分別是何時調用:

//1. xml 佈局文件
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 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:id="@+id/property_animation_root_container"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@color/white"
    tools:context=".propertyanimation.PropertyAnimationListenerActivity">

    <ImageView
        android:id="@+id/property_animation_target"
        android:layout_width="@dimen/avatar_size_xxx"
        android:layout_height="@dimen/avatar_size_xxx"
        android:layout_centerHorizontal="true"
        android:layout_marginTop="@dimen/item_height"
        android:src="@drawable/bird_dove" />

    <LinearLayout
        android:id="@+id/property_animation_container"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:orientation="horizontal">

        <Button
            android:id="@+id/property_animation_start"
            style="@style/TweenAnimationActivityButton"
            android:layout_marginLeft="@dimen/small_padding"
            android:text="@string/start" />

        <Button
            android:id="@+id/property_animation_pause"
            style="@style/TweenAnimationActivityButton"
            android:text="@string/pause" />

        <Button
            android:id="@+id/property_animation_cancel"
            style="@style/TweenAnimationActivityButton"
            android:text="@string/cancel" />

        <Button
            android:id="@+id/property_animation_stop"
            style="@style/TweenAnimationActivityButton"
            android:text="@string/stop" />
    </LinearLayout>
</RelativeLayout>

//2. 經過代碼監聽 AnimatorListener、AnimatorPauseListener 和 AnimatorUpdateListener
public class PropertyAnimationListenerActivity extends AppCompatActivity implements View.OnClickListener, ValueAnimator.AnimatorUpdateListener, Animator.AnimatorListener,Animator.AnimatorPauseListener {

    private ImageView mTarget;
    private Button mStart,mPause,mCancel,mStop;
    private ValueAnimator mValueAnimator;

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

    private void initView(){
        mTarget = findViewById(R.id.property_animation_target);
        mStart = findViewById(R.id.property_animation_start);
        mPause = findViewById(R.id.property_animation_pause);
        mCancel = findViewById(R.id.property_animation_cancel);
        mStop = findViewById(R.id.property_animation_stop);
        mStart.setOnClickListener(this);
        mPause.setOnClickListener(this);
        mCancel.setOnClickListener(this);
        mStop.setOnClickListener(this);
    }

    private void initData(){
        setTitle(R.string.property_animation_listener);
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()){
            case R.id.property_animation_start:
                startAnimation();
                break;
            case R.id.property_animation_pause:
                pauseAnimation();
                break;
            case R.id.property_animation_cancel:
                cancelAnimation();
                break;
            case R.id.property_animation_stop:
                stopAnimation();
                break;
        }
    }

    private void startAnimation(){
        if(mValueAnimator == null){
            mValueAnimator = ValueAnimator.ofFloat(getResources().getInteger(R.integer.integer_zero), getResources().getInteger(R.integer.integer_eight_hundred));
            mValueAnimator.setDuration(getResources().getInteger(R.integer.integer_one_thousand_and_eight_hundred));
            mValueAnimator.setInterpolator(new AccelerateDecelerateInterpolator());
            mValueAnimator.setRepeatCount(ValueAnimator.INFINITE);
            mValueAnimator.setRepeatMode(ValueAnimator.REVERSE);
            mValueAnimator.addUpdateListener(this);
            mValueAnimator.addListener(this);
            mValueAnimator.addPauseListener(this);
            mValueAnimator.start();
        }else if(mValueAnimator.isPaused()){
            mValueAnimator.resume();
        }else if(!mValueAnimator.isStarted()){
            mValueAnimator.start();
        }

    }

    private void pauseAnimation(){
        if(mValueAnimator != null && !mValueAnimator.isPaused()){
            mValueAnimator.pause();
        }
    }

    private void cancelAnimation(){
        if(mValueAnimator != null){
            mValueAnimator.cancel();
        }
    }

    private void stopAnimation(){
        if(mValueAnimator != null){
            mValueAnimator.end();
        }
    }

    @Override
    public void onAnimationUpdate(ValueAnimator animation) {
        if(Constants.IS_DEBUG){
            Log.e(Constants.TAG, " onAnimationUpdate ");
        }
        mTarget.setY((Float) animation.getAnimatedValue());
    }

    @Override
    public void onAnimationStart(Animator animation) {
        if(Constants.IS_DEBUG){
            Log.e(Constants.TAG, " onAnimationStart ");
        }
    }

    @Override
    public void onAnimationEnd(Animator animation) {
        if(Constants.IS_DEBUG){
            Log.e(Constants.TAG, " onAnimationEnd ");
        }
    }

    @Override
    public void onAnimationCancel(Animator animation) {
        if(Constants.IS_DEBUG){
            Log.e(Constants.TAG, " onAnimationCancel ");
        }
    }

    @Override
    public void onAnimationRepeat(Animator animation) {
        if(Constants.IS_DEBUG){
            Log.e(Constants.TAG, " onAnimationRepeat ");
        }
    }

    @Override
    public void onAnimationPause(Animator animation) {
        if(Constants.IS_DEBUG){
            Log.e(Constants.TAG, " onAnimationPause ");
        }
    }

    @Override
    public void onAnimationResume(Animator animation) {
        if(Constants.IS_DEBUG){
            Log.e(Constants.TAG, " onAnimationResume ");
        }
    }

}
複製代碼

最終效果以下:

操做步驟以下:

  1. start-->pause-->resume-->cancel;
  2. start-->stop;

Log 輸出的信息爲:

2019-03-17 18:16:04.493 23612-23612/com.smart.animationanalyse E/TAG:   onAnimationStart  
2019-03-17 18:16:04.494 23612-23612/com.smart.animationanalyse E/TAG:   onAnimationUpdate  
2019-03-17 18:16:05.134 23612-23612/com.smart.animationanalyse E/TAG:   onAnimationUpdate  
2019-03-17 18:16:05.152 23612-23612/com.smart.animationanalyse E/TAG:   onAnimationUpdate  
2019-03-17 18:16:05.861 23612-23612/com.smart.animationanalyse E/TAG:   onAnimationUpdate  
2019-03-17 18:16:05.863 23612-23612/com.smart.animationanalyse E/TAG:   onAnimationPause  
2019-03-17 18:16:07.045 23612-23612/com.smart.animationanalyse E/TAG:   onAnimationResume  
2019-03-17 18:16:07.056 23612-23612/com.smart.animationanalyse E/TAG:   onAnimationUpdate  
2019-03-17 18:16:07.140 23612-23612/com.smart.animationanalyse E/TAG:   onAnimationUpdate  
2019-03-17 18:16:07.164 23612-23612/com.smart.animationanalyse E/TAG:   onAnimationUpdate  
2019-03-17 18:16:07.472 23612-23612/com.smart.animationanalyse E/TAG:   onAnimationUpdate  
2019-03-17 18:16:07.490 23612-23612/com.smart.animationanalyse E/TAG:   onAnimationRepeat  
2019-03-17 18:16:07.491 23612-23612/com.smart.animationanalyse E/TAG:   onAnimationUpdate  
2019-03-17 18:16:08.161 23612-23612/com.smart.animationanalyse E/TAG:   onAnimationUpdate  
2019-03-17 18:16:08.177 23612-23612/com.smart.animationanalyse E/TAG:   onAnimationUpdate  
2019-03-17 18:16:08.757 23612-23612/com.smart.animationanalyse E/TAG:   onAnimationUpdate  
2019-03-17 18:16:08.759 23612-23612/com.smart.animationanalyse E/TAG:   onAnimationCancel  
2019-03-17 18:16:08.759 23612-23612/com.smart.animationanalyse E/TAG:   onAnimationEnd  
2019-03-17 18:16:10.495 23612-23612/com.smart.animationanalyse E/TAG:   onAnimationStart  
2019-03-17 18:16:10.496 23612-23612/com.smart.animationanalyse E/TAG:   onAnimationUpdate  
2019-03-17 18:16:11.159 23612-23612/com.smart.animationanalyse E/TAG:   onAnimationUpdate  
2019-03-17 18:16:11.174 23612-23612/com.smart.animationanalyse E/TAG:   onAnimationUpdate  
2019-03-17 18:16:11.932 23612-23612/com.smart.animationanalyse E/TAG:   onAnimationUpdate  
2019-03-17 18:16:11.933 23612-23612/com.smart.animationanalyse E/TAG:   onAnimationEnd 
複製代碼

2.4 屬性動畫工做原理

屬性動畫的工做原理,大體以下:

當 ValueAnimator 調用 start 方法以後,ValueAnimator 會根據 Property Animation 當前運行時間與總的動畫持續時間計算出一個時間消耗百分數(The elapsed fraction)。緊接着,ValueAnimator 將這個時間消耗百分數交給當前 ValueAnimator 的插值器(Interpolator),不一樣的 Interpolator 會根據不一樣的算法將這個時間消耗百分數轉換成插值百分數(The interpolated fraction)。緊接着,ValueAnimator 會將這個插值百分數交給當前 ValueAnimator 的估值器(TypeEvaluator),不一樣的 TypeEvaluator 會根據不一樣的算法將這個插值百分數轉換最終的動畫值(The final value)。

上面的文字能夠用函數表示爲:

The interpolated fraction = f(The elapsed fraction);
The final value = h(The interpolated fraction);

舉個例子:

上面這個屬性動畫的 Duration 爲 40ms,Intepolator 爲 LinearInterpolator,Distance 爲 40。

在 t = 10ms 時,The elapsed fraction 爲 0.25 = 10/40,The interpolated fraction = 0.25,The final value 爲 10 = (40 - 0) * 0.25。

有些人可能不懂爲何此處 The interpolated fraction 和 The elapsed fraction 相等,其實只要看下 LinearInterpolator 源碼就懂啦:

public class LinearInterpolator extends BaseInterpolator implements NativeInterpolatorFactory {

    public LinearInterpolator() {
    }

    public LinearInterpolator(Context context, AttributeSet attrs) {
    }

    //此處的 input 即爲 The elapsed fraction,LinearInterpolator 並未對其進行任何特殊處理,而是直接將其返回,
    //而這個返回值就是 The interpolated fraction,因此此處 The interpolated fraction 和 The elapsed fraction 相等。
    public float getInterpolation(float input) {
        return input;
    }

    /** @hide */
    @Override
    public long createNativeInterpolator() {
        return NativeInterpolatorFactoryHelper.createLinearInterpolator();
    }
}
複製代碼

上面這個屬性動畫的 Duration 爲 40ms,Intepolator 爲 AccelerateDecelerateInterpolator,Distance 爲 40。

在 t = 10ms 時,The elapsed fraction 爲 0.25 = 10/40,The interpolated fraction = 0.14644662,The final value 爲 5.8578648 = (40 - 0) * 0.14644662。

有些人可能不知道爲何 The interpolated fraction 的值爲 0.14644662,其實只要看下 LinearInterpolator 源碼就懂啦:

public class AccelerateDecelerateInterpolator extends BaseInterpolator
        implements NativeInterpolatorFactory {
    public AccelerateDecelerateInterpolator() {
    }

    @SuppressWarnings({"UnusedDeclaration"})
    public AccelerateDecelerateInterpolator(Context context, AttributeSet attrs) {
    }

    public float getInterpolation(float input) {
        return (float)(Math.cos((input + 1) * Math.PI) / 2.0f) + 0.5f;
    }

    /** @hide */
    @Override
    public long createNativeInterpolator() {
        return NativeInterpolatorFactoryHelper.createAccelerateDecelerateInterpolator();
    }
}
複製代碼

The interpolated fraction = (float)(Math.cos((0.25 + 1) * Math.PI) / 2.0f) + 0.5f = 0.14644662

2.5 自定義插值器

Property Animation 中用的插值器與 Tween Animation 中的同樣,所以在此不贅述,想要了解如何自定義插值器,請查閱《這一次讓你完全瞭解 Android Tween Animation》

此處,僅結合《這一次讓你完全瞭解 Android Tween Animation》一文中定義的 DecelerateAccelerateInterpolator 和 ValueAnimator 作個簡單的示例:

//1. 自定義減速加速插值器
public class DecelerateAccelerateInterpolator implements Interpolator {

    @Override
    public float getInterpolation(float input) {
        return (float) ((Math.tan(Math.PI/2 * input - Math.PI/4) + 1)/2);
    }

}

//2. 應用自定義插值器
public class PropertyAnimationListenerActivity extends AppCompatActivity implements View.OnClickListener, ValueAnimator.AnimatorUpdateListener, Animator.AnimatorListener,Animator.AnimatorPauseListener {

    private ImageView mTarget;
    private Button mStart,mPause,mCancel,mStop;
    private ValueAnimator mValueAnimator;

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

    private void initView(){
        mTarget = findViewById(R.id.property_animation_target);
        mStart = findViewById(R.id.property_animation_start);
        mPause = findViewById(R.id.property_animation_pause);
        mCancel = findViewById(R.id.property_animation_cancel);
        mStop = findViewById(R.id.property_animation_stop);
        mStart.setOnClickListener(this);
        mPause.setOnClickListener(this);
        mCancel.setOnClickListener(this);
        mStop.setOnClickListener(this);
    }

    private void initData(){
        setTitle(R.string.property_animation_custom_interpolator_value_animator);
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()){
            case R.id.property_animation_start:
                startAnimation();
                break;
            case R.id.property_animation_pause:
                pauseAnimation();
                break;
            case R.id.property_animation_cancel:
                cancelAnimation();
                break;
            case R.id.property_animation_stop:
                stopAnimation();
                break;
        }
    }

    private void startAnimation(){
        if(mValueAnimator == null){
            mValueAnimator = ValueAnimator.ofFloat(getResources().getInteger(R.integer.integer_zero), getResources().getInteger(R.integer.integer_eight_hundred));
            mValueAnimator.setDuration(getResources().getInteger(R.integer.integer_one_thousand_and_eight_hundred));
            mValueAnimator.setInterpolator(new DecelerateAccelerateInterpolator());
            mValueAnimator.setRepeatCount(ValueAnimator.INFINITE);
            mValueAnimator.setRepeatMode(ValueAnimator.REVERSE);
            mValueAnimator.addUpdateListener(this);
            mValueAnimator.addListener(this);
            mValueAnimator.addPauseListener(this);
            mValueAnimator.start();
        }else if(mValueAnimator.isPaused()){
            mValueAnimator.resume();
        }else if(!mValueAnimator.isStarted()){
            mValueAnimator.start();
        }


    }

    private void pauseAnimation(){
        if(mValueAnimator != null && !mValueAnimator.isPaused()){
            mValueAnimator.pause();
        }
    }

    private void cancelAnimation(){
        if(mValueAnimator != null){
            mValueAnimator.cancel();
        }
    }

    private void stopAnimation(){
        if(mValueAnimator != null){
            mValueAnimator.end();
        }
    }

    @Override
    public void onAnimationUpdate(ValueAnimator animation) {
        if(Constants.IS_DEBUG){
            Log.e(Constants.TAG, " onAnimationUpdate ");
        }
        mTarget.setY((Float) animation.getAnimatedValue());
    }

    @Override
    public void onAnimationStart(Animator animation) {
        if(Constants.IS_DEBUG){
            Log.e(Constants.TAG, " onAnimationStart ");
            Log.e(Constants.TAG, String.valueOf((float)(Math.cos((0.25 + 1) * Math.PI) / 2.0f) + 0.5f));
        }
    }

    @Override
    public void onAnimationEnd(Animator animation) {
        if(Constants.IS_DEBUG){
            Log.e(Constants.TAG, " onAnimationEnd ");
        }
    }

    @Override
    public void onAnimationCancel(Animator animation) {
        if(Constants.IS_DEBUG){
            Log.e(Constants.TAG, " onAnimationCancel ");
        }
    }

    @Override
    public void onAnimationRepeat(Animator animation) {
        if(Constants.IS_DEBUG){
            Log.e(Constants.TAG, " onAnimationRepeat ");
        }
    }

    @Override
    public void onAnimationPause(Animator animation) {
        if(Constants.IS_DEBUG){
            Log.e(Constants.TAG, " onAnimationPause ");
        }
    }

    @Override
    public void onAnimationResume(Animator animation) {
        if(Constants.IS_DEBUG){
            Log.e(Constants.TAG, " onAnimationResume ");
        }
    }

}
複製代碼

最終效果以下:

2.6 自定義估值器

想要自定義估值器,只要實現 TypeEvaluator 接口,並實現其中定義的 evaluate 方法便可。

接下來,咱們自定義一個估值器:

//1. 自定義估值器
public class CustomTypeEvaluator implements TypeEvaluator {

    @Override
    public Object evaluate(float fraction, Object startValue, Object endValue) {
        float startFloat = ((Number) startValue).floatValue();
        return 200 + fraction * (((Number) endValue).floatValue() - startFloat);
    }
    
}

//2. 應用自定義估值器
public class PropertyAnimationListenerActivity extends AppCompatActivity implements View.OnClickListener, ValueAnimator.AnimatorUpdateListener, Animator.AnimatorListener,Animator.AnimatorPauseListener {

    private ImageView mTarget;
    private Button mStart,mPause,mCancel,mStop;
    private ValueAnimator mValueAnimator;

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

    private void initView(){
        mTarget = findViewById(R.id.property_animation_target);
        mStart = findViewById(R.id.property_animation_start);
        mPause = findViewById(R.id.property_animation_pause);
        mCancel = findViewById(R.id.property_animation_cancel);
        mStop = findViewById(R.id.property_animation_stop);
        mStart.setOnClickListener(this);
        mPause.setOnClickListener(this);
        mCancel.setOnClickListener(this);
        mStop.setOnClickListener(this);
    }

    private void initData(){
        setTitle(R.string.property_animation_custom_typeEvaluator_value_animator);
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()){
            case R.id.property_animation_start:
                startAnimation();
                break;
            case R.id.property_animation_pause:
                pauseAnimation();
                break;
            case R.id.property_animation_cancel:
                cancelAnimation();
                break;
            case R.id.property_animation_stop:
                stopAnimation();
                break;
        }
    }

    private void startAnimation(){
        if(mValueAnimator == null){
            mValueAnimator = ValueAnimator.ofFloat(getResources().getInteger(R.integer.integer_zero), getResources().getInteger(R.integer.integer_eight_hundred));
            mValueAnimator.setDuration(getResources().getInteger(R.integer.integer_one_thousand_and_eight_hundred));
            mValueAnimator.setInterpolator(new DecelerateAccelerateInterpolator());
            mValueAnimator.setEvaluator(new CustomTypeEvaluator());
            mValueAnimator.setRepeatCount(ValueAnimator.INFINITE);
            mValueAnimator.setRepeatMode(ValueAnimator.REVERSE);
            mValueAnimator.addUpdateListener(this);
            mValueAnimator.addListener(this);
            mValueAnimator.addPauseListener(this);
            mValueAnimator.start();
        }else if(mValueAnimator.isPaused()){
            mValueAnimator.resume();
        }else if(!mValueAnimator.isStarted()){
            mValueAnimator.start();
        }


    }

    private void pauseAnimation(){
        if(mValueAnimator != null && !mValueAnimator.isPaused()){
            mValueAnimator.pause();
        }
    }

    private void cancelAnimation(){
        if(mValueAnimator != null){
            mValueAnimator.cancel();
        }
    }

    private void stopAnimation(){
        if(mValueAnimator != null){
            mValueAnimator.end();
        }
    }

    @Override
    public void onAnimationUpdate(ValueAnimator animation) {
        if(Constants.IS_DEBUG){
            Log.e(Constants.TAG, " onAnimationUpdate ");
        }
        mTarget.setY((Float) animation.getAnimatedValue());
    }

    @Override
    public void onAnimationStart(Animator animation) {
        if(Constants.IS_DEBUG){
            Log.e(Constants.TAG, " onAnimationStart ");
            Log.e(Constants.TAG, String.valueOf((float)(Math.cos((0.25 + 1) * Math.PI) / 2.0f) + 0.5f));
        }
    }

    @Override
    public void onAnimationEnd(Animator animation) {
        if(Constants.IS_DEBUG){
            Log.e(Constants.TAG, " onAnimationEnd ");
        }
    }

    @Override
    public void onAnimationCancel(Animator animation) {
        if(Constants.IS_DEBUG){
            Log.e(Constants.TAG, " onAnimationCancel ");
        }
    }

    @Override
    public void onAnimationRepeat(Animator animation) {
        if(Constants.IS_DEBUG){
            Log.e(Constants.TAG, " onAnimationRepeat ");
        }
    }

    @Override
    public void onAnimationPause(Animator animation) {
        if(Constants.IS_DEBUG){
            Log.e(Constants.TAG, " onAnimationPause ");
        }
    }

    @Override
    public void onAnimationResume(Animator animation) {
        if(Constants.IS_DEBUG){
            Log.e(Constants.TAG, " onAnimationResume ");
        }
    }

}
複製代碼

最終效果以下:

下面使用默認插值器的效果圖:

2.7 ViewPropertyAnimator 使用簡介

當須要同時更改 View 的多個屬性的時候,我知道三種方法:

  1. ObjectAnimator + AnimatorSet;
  2. PropertyValuesHolder + ObjectAnimator;
  3. ViewPropertyAnimator;

接下來,分別用三種方法分別實現同一種效果:

View 的 Y 值從當前位置增到 400,Alpha 值 從 1.0f 變成 0.1f。

2.7.1 ObjectAnimator + AnimatorSet
ObjectAnimator alphaObjectAnimator = ObjectAnimator.ofFloat(mTarget, "alpha", 1.0f, 0.1f);
ObjectAnimator yObjectAnimator = ObjectAnimator.ofFloat(mTarget, "y", 400f);
AnimatorSet animatorSet = new AnimatorSet();
animatorSet.playTogether(alphaObjectAnimator, yObjectAnimator);
animatorSet.start();
複製代碼

最終效果以下:

2.7.2 PropertyValuesHolder + ObjectAnimator
PropertyValuesHolder alphaPropertyValuesHolder = PropertyValuesHolder.ofFloat("alpha", 1.0f, 0.1f);
PropertyValuesHolder yPropertyValuesHolder = PropertyValuesHolder.ofFloat("y", 400f);
ObjectAnimator.ofPropertyValuesHolder(mTarget, alphaPropertyValuesHolder, yPropertyValuesHolder).start();
複製代碼

最終效果以下:

2.7.3 ViewPropertyAnimator
ViewPropertyAnimator viewPropertyAnimator = mTarget.animate();
viewPropertyAnimator.alpha(0.1f);
viewPropertyAnimator.y(400f);

//也能夠寫成一句:
mTarget.animate().alpha(0.1f).y(400f);
複製代碼

最終效果以下:

相比於上面兩種實現,經過 ViewPropertyAnimator 實現是否是更簡單?類似的簡單操做類還有 ViewAnimationUtils。

3. 應用實例

Property Animation 的應用場景仍是不少的,全部能用 Tween Animation 實現的動畫都能經過 Property Animation 實現,另外,大多數的自定義 View 中都有 Property Animation 的身影,以下面這些都是我經過自定義 View + Property Animation 實現的:

4. 參考文獻

  1. Animation resources
  2. Property Animation Overview
相關文章
相關標籤/搜索