阅读 1125

Android Animation:这一次让你彻底了解 Android Property Animation

在正式开始讲解 Property Animation 之前,先放一张用 Property Animation 实现的效果图,有兴趣的小伙伴可以先自行尝试下:

1. 属性动画概述

1.1 概念

在一段时间内通过修改对象的属性而形成的动画叫属性动画。

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

1.2 属性动画的作用是什么?

从上面的定义可知,属性动画的主要是修改对象的属性,如 View 的背景颜色、透明值、位置等。

1.3 为什么 Google 官方会在 Android 3.0 的时候添加 Property Animation?

不是已经有 Tween Animation 了吗,为什么还会有 Property Animation?换句话说,Property Animation 到底能干哪些 Tween Animation 不能干的活?想明白了这些问题,自然就明白了为什么 Google 官方会在 Android 3.0 添加 Property Animation。

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 就不能作用于它的属性:

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。

  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
关注下面的标签,发现更多相似文章
评论