属性动画(一)

接下来的两篇文章会介绍 属性动画 相关的知识,如下图所示。本篇文章会介绍下图中绿色相关的知识。



1. 简介

相比视图动画(View Animation),属性动画的功能还是非常强大的。

属性动画不仅可以对视图(View)的位置、大小、透明度、旋转进行动画操作,而且对于视图的背景颜色等属性也可以改变。

除此之外,属性动画还可以作用于视图之外的对象,比如自定义的类的对象。

一句话,只要是对象的属性都可以被属性动画所操纵。

因为还是属于动画的概念,所以需要设定一些动画相关的特性:

  • 持续时间:动画持续的时间,默认是 300 ms
  • 时间插值器(Time interpolation):用于定义属性的变化率,即用于计算在某一时刻,属性的值是多少
  • 重复次数和行为:定义是否重复和重复次数,行为表示是否翻转动画
  • 动画集合(Animator sets):可以将多个动画设置到一个集合中,并控制其中的执行顺序
  • 动画帧刷新频率(Frame refresh delay):动画多久刷新一帧,默认是每 10ms 刷新一帧

2. 工作原理

2.1 匀速平移动画

如下图所示,是一个简单动画的执行过程的示意图:



  • 此示意图中的动画是匀速进行的
  • 40ms 的时间内, View 沿 x 轴向右平移 40px。每隔 10ms 都向右平移 10px,最后再 40ms 的时候,View 停在了 40px 的位置

2.2 非匀速平移动画



  • 此示意图中的动画是非匀速进行的
  • 40ms 的时间内, View 沿 x 轴向右平移 40px。在初始阶段,View 是加速运动的,一直加速到中点;中点之后,开始减速运动,到达终点时,速度减为0。
  • 在初始阶段和结束阶段,同样的时间内,View 运动的距离比在中点附近同样时间内运动的距离短,因为中点附近时的速度是最大的。

2.3 属性值的计算原理



  • 此示意图说明了在属性动画中,属性值是怎样计算出来的
  • ValueAnimator 用于追踪动画时间,比如:动画已经运行了多长时间,在当前时间点的属性值是多少
  • ValueAnimator 中包含两个对象:TimeInterpolatorTypeEvaluatorTimeInterpolator 是动画插值器,TypeEvaluator 用于定义在某一时间点,属性值该怎么计算
  • 动画开始时,创建一个 ValueAnimator 对象,并将属性值的初始值、结束值和动画执行时间赋予它。当调用 start() 方法时,动画开始。在整个动画执行期间,ValueAnimator 会基于动画的总时长和已运行的动画时间,计算动画的时间完成度(elapsed fraction),完成度值的范围是 01
  • ValueAnimator 计算完成时间完成度之后,它会调用当前设置的插值器 TimeInterpolator 计算插值完成度(interpolated fraction)。插值完成度是由时间完成度计算得到的。
  • 当计算得到插值完成度(interpolated fraction)之后,根据动画设置的初始值、结束值和插值完成度,通过 TypeEvaluator 即可计算得到当前的属性值。

3. 和视图动画的区别

视图动画(View animation)具有如下局限性:

  • 视图动画(View animation)只可以作用于视图(View)对象,对于非视图对象则难以处理,视图动画只可以改变视图的位置、大小、透明度和旋转,对于视图的其他属性也没办法改变。
  • 视图动画只是改变了视图显示的样式,并没有真正的改变视图的属性值

属性动画则视图动画的局限性,属性动画可以作用于视图对象和非视图对象,并且可以改变任何对象的任何属性。例如,可以作用于自定义的对象的属性值,也可以作用于视图对象的背景颜色、位置大小等属性。

4. 相关 API 一览

android.animation 包下可以找到属性动画相关的所有类,视图动画相关的类定义在 android.view.animation 包下。

Class Description
ValueAnimator ValueAnimator 是属性动画的核心类,其中包含动画相关的关键信息,包括动画时间相关的细节、动画是否重复、接收更新时间的监听器等等。属性动画包括两部分:1. 计算属性值;2. 将计算得到的属性值赋予对象的属性。ValueAnimator 并不会执行第二部,所以需要开发者监听属性值的变化,并根据你自己的逻辑更新对象的属性。
ObjectAnimator ObjectAnimatorValueAnimator 类的子类,它允许开发者设置目标对象和动画操纵的属性。在计算得到新的属性值时,这个类会相应地更新对象的属性。大多数时候,使用 ObjectAnimator 都是方便的,但有时候也需要使用 ValueAnimator,因为 ObjectAnimator 有一些限制,例如 ObjectAnimator 需要目标类有相应的访问目标属性的方法
AnimatorSet AnimatorSet 提供了一种将动画合并到一起的机制,比如将多个动画同时播放、按顺序播放等。

Evaluators 用于告诉属性动画系统怎么计算属性值。它根据 Animator 提供的时间值(开始时间和结束时间),计算当前的属性值。SDK 提供以下 evaluators

Class/Interface Description
IntEvaluator 用于计算 int 类型的属性值
FloatEvaluator 用于计算 Float 类型的属性值
AnimatorSet 用于计算 ARGB 颜色类型的属性值
TypeEvaluator 用于实现自定义属性值变化的 evaluator。如果你正在作用的对象的属性不是 intfloatargb 类型的,必须实现 TypeEvaluator 的子类去定义该属性值怎么变化。

插值器 Interpolators 是用于属性值的变化率。比如是属性值是加速变化的、减速变化的、还是先加速再减速。如果默认提供的 Interpolators 不能满足开发者的需求,则可以实现 TimeInterpolator 接口自定义插值器,规定属性值该怎样从初始值变化到最终值。

Class/Interface Description
AccelerateDecelerateInterpolator 先加速再减速(默认的插值器
AccelerateInterpolator 持续加速
AnticipateInterpolator 先向反向变化一下,再向前运动
OvershootInterpolator 动画到达终点时,会先超过一点,再回缩到终点值
AnticipateOvershootInterpolator 先反向,再向前运动,超过终点值一点,再回缩到终点值
BounceInterpolator 会在目标值处弹跳
CycleInterpolator 正弦 / 余弦曲线变化率
DecelerateInterpolator 持续减速直到0
LinearInterpolator 匀速
TimeInterpolator 实现自定义插值器时需要实现的接口

4.1 ViewPropertyAnimator

ViewPropertyAnimator 是实现视图(View)类对象属性动画非常方便的类

  • 当同时有多个视图对象的属性需要更新时,使用 ViewPropertyAnimator 的性能会比使用 ObjectAnimator 更好。因为 ViewPropertyAnimator 会一次性同时更新多个属性值并绘制,而 ObjectAnimator 则是将多个属性值分别更新并绘制
  • 使用 ViewPropertyAnimator 实现属性动画的语法也比使用 ObjectAnimator 实现属性动画的语法更简单

ViewPropertyAnimator 的使用方法如下所示:

1
2
3
4
5
6
7
// 表示将此按钮 translationX 值渐变为 500
Button button = (Button) findViewById(R.id.btn);
button.animate()
.translationX(500)
// .translationXBy(500) 表示将 translationX 渐变地增加 500
.setDuration(1000)
.start();

ViewPropertyAnimator 在实现视图类对象的位置、大小、透明度、旋转的动画的时候,是非常方便的,使用方法和上面代码非常类似,具体可以参照 API 文档和源码注释。

4.2 ObjectAnimator

ObjectAnimator 的用法也非常简单,如下代码实现了 Button 按钮从 0px 向右平移到 500px 位置的动画

1
2
3
4
Button button = (Button) findViewById(R.id.btn);
ObjectAnimator animator = ObjectAnimator.ofFloat(button,"translationX",0f,500f);
animator.setDuration(800);
animator.start();

若要正确使用 ObjectAnimator,需要注意以下几点:

  • 动画作用的属性需要有一个名为 setXXX() 的方法,因为属性动画更新对象的属性值,所以该对象必须要有一个可以修改该属性的 setXXX() 方法。比如,若动画作用的属性名为 foo,则该对象必须要拥有一个名为 setFoo() 的方法供属性动画使用。若没有这样的方法,有以下几个解决办法:
    • 如果该类可以修改,增加对应的 setXXX() 方法
    • 如果可以修改该属性,可以使用一个包装类,增加对应的 setXXX() 方法,将接收到的属性更改值,传递到原始对象的属性
    • 使用 ValueAnimator,而不是 ObjectAnimator
  • 如果在 ObjectAnimator 的工厂方法中只传递进去一个属性值,则该属性值是该动画的结束值。所以此对象的这个属性值也必须拥有一个 getXXX() 方法,用于获得动画的初始值。例如,若动画作用的属性名为 foo,则该类需要有一个 getFoo() 的方法
  • 属性的 getXXX() 方法和 setXXX() 方法中参数的类型,必须和 ObjectAnimator 静态工厂中的方法的类型相同。例如,若有一个对象是 targetObject.setPropName(float)targetObject.getPropName(float),则创建对应的 ObjectAnimator 对象时,需要时如下的代码:

    1
    ObjectAnimator.ofFloat(targetObject, "propName", 1f)
  • 对于有些对象的属性,如果目标对象是 View 对象时,属性动画更新属性值的时候,需要调用 invalidate() 方法强制地重新绘制这个 View 对象。此步骤发生在 onAnimationUpdate() 回调方法中。例如,若属性动画作用于一个 Drawable 对象的颜色时,只有重新绘制该 Drawable 对象,则颜色属性才会生效。对于 View 对象的 setAlpha()setTranslationX()等属性值,在属性动画作用的时候,都会正确地重绘,所以不必关心它们。

4.3 ValueAnimator

ValueAnimator 可以在某一段时间内,对一些类型的值进行改变。通过调用它的 ofInt()ofFloat()ofObject() 工厂方法可以得到 ValueAnimator 的对象。例如:

1
2
3
ValueAnimator animation = ValueAnimator.ofFloat(0f, 100f);
animation.setDuration(1000);
animation.start();

上述代码,当调用 start() 方法动画开始时,动画的值在 1000ms 内,从 0 变化到了 100
可以为 ValueAnimator 添加监听器,监测在这段时间内动画值的变化情况,如下代码所示:

1
2
3
4
5
6
7
8
9
10
animation.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator updatedAnimation) {
// You can use the animated value in a property that uses the
// same type as the animation. In this case, you can use the
// float value in the translationX property.
float animatedValue = (float)updatedAnimation.getAnimatedValue();
textView.setTranslationX(animatedValue);
}
});

4.4 AnimatorSet

在许多情况下,一个动画需要在另一个动画开始或结束时开始执行。系统提供的 AnimatorSet 类可以将多个动画组合在一起,动画之间和同时播放,可以按顺序播放或延时播放。

同样,也可以将多个 AnimatorSet 嵌套使用。

下面是一个使用 AnimatorSet 播放组合动画的例子:

  • 首先播放 bounceAnim
  • 然后同时播放 squashAnim1, squashAnim2, stretchAnim1stretchAnim2
  • 接着播放 bounceBackAnim
  • 最后播放 fadeAnim
    代码如下所示:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    AnimatorSet bouncer = new AnimatorSet();
    bouncer.play(bounceAnim).before(squashAnim1);
    bouncer.play(squashAnim1).with(squashAnim2);
    bouncer.play(squashAnim1).with(stretchAnim1);
    bouncer.play(squashAnim1).with(stretchAnim2);
    bouncer.play(bounceBackAnim).after(stretchAnim2);
    ValueAnimator fadeAnim = ObjectAnimator.ofFloat(newBall, "alpha", 1f, 0f);
    fadeAnim.setDuration(250);
    AnimatorSet animatorSet = new AnimatorSet();
    animatorSet.play(bouncer).before(fadeAnim);
    animatorSet.start();

4.5 PropertyValuesHolder

PropertyValuesHolder 可以实现在一个动画中,多个属性同时变化的情况,比如:一个 Button 按钮,在从小不断变大的过程中,透明度也在同时发生着变化,代码如下所示:

1
2
3
4
5
6
7
8
Button button = (Button) findViewById(R.id.btn);
PropertyValuesHolder holder1 = PropertyValuesHolder.ofFloat("scaleX", 1);
PropertyValuesHolder holder2 = PropertyValuesHolder.ofFloat("scaleY", 1);
PropertyValuesHolder holder3 = PropertyValuesHolder.ofFloat("alpha", 1);
ObjectAnimator animator = ObjectAnimator.ofPropertyValuesHolder(button, holder1, holder2, holder3)
animator.setDuration(800);
animator.start();

提到 PropertyValuesHolder,还有一个类也需要提一下 — Keyframe(关键帧)。通过 Keyframe 可以将一个动画拆分成多个阶段。如下代码所示:

1
2
3
4
5
6
7
8
9
10
11
Button button = (Button) findViewById(R.id.btn);
// 在 0% 处开始
Keyframe keyframe1 = Keyframe.ofFloat(0, 0);
// 时间经过 50% 的时候,动画完成度 100%
Keyframe keyframe2 = Keyframe.ofFloat(0.5f, 100);
// 时间见过 100% 的时候,动画完成度倒退到 80%,即反弹 20%
Keyframe keyframe3 = Keyframe.ofFloat(1, 80);
PropertyValuesHolder holder = PropertyValuesHolder.ofKeyframe("scaleX", keyframe1, keyframe2, keyframe3);
ObjectAnimator animator = ObjectAnimator.ofPropertyValuesHolder(button, holder);
animator.start();

4.6 动画监听器

在动画运行期间,可以通过以下监听器监听动画的关键时刻:

1
2
3
4
5
6
7
8
9
10
11
12
13
public static interface AnimatorListener {
// 在动画开始时被调用
void onAnimationStart(Animator animation);
// 在动画结束时被调用
void onAnimationEnd(Animator animation);
// 在动画取消时被调用
void onAnimationCancel(Animator animation);
// 在动画重复执行时被调用
void onAnimationRepeat(Animator animation);
}

1
2
3
4
5
6
7
8
public static interface AnimatorPauseListener {
//在动画暂停时被调用
void onAnimationPause(Animator animation);
// 在动画被重新执行时被调用
void onAnimationResume(Animator animation);
}
1
2
3
4
public static interface AnimatorUpdateListener {
// 在动画刷新每一帧的时候都会被调用
void onAnimationUpdate(ValueAnimator animation);
}
  • AnimatorUpdateListener.onAnimationUpdate(ValueAnimator animation) 方法中有 animation 参数,通过参数 animation.getAnimatedValue() 可以得到当前动画执行的完成度的值。

  • 如果不想实现 AnimatorListener 接口的所有方法,也可以继承 AnimatorListenerAdapter,选择想实现的方法。例如,以下代码通过实现 AnimatorListenerAdapter 匿名内部类的方式,只实现了 onAnimationEnd() 回调方法

    1
    2
    3
    4
    5
    6
    ValueAnimator fadeAnim = ObjectAnimator.ofFloat(newBall, "alpha", 1f, 0f);
    fadeAnim.setDuration(250);
    fadeAnim.addListener(new AnimatorListenerAdapter() {
    public void onAnimationEnd(Animator animation) {
    balls.remove(((ObjectAnimator)animation).getTarget());
    }

参考资料:

官方文档

Android基础——动画(二)林于卫国

HenCoder Android 自定义 View 1-7:属性动画 Property Animation(进阶篇)HenCoder