接下来的两篇文章会介绍 属性动画
相关的知识,如下图所示。本篇文章会介绍下图中绿色相关的知识。
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
中包含两个对象:TimeInterpolator
和TypeEvaluator
,TimeInterpolator
是动画插值器,TypeEvaluator
用于定义在某一时间点,属性值该怎么计算 - 动画开始时,创建一个
ValueAnimator
对象,并将属性值的初始值、结束值和动画执行时间赋予它。当调用start()
方法时,动画开始。在整个动画执行期间,ValueAnimator
会基于动画的总时长和已运行的动画时间,计算动画的时间完成度(elapsed fraction
),完成度值的范围是0
到1
。 - 当
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 |
ObjectAnimator 是 ValueAnimator 类的子类,它允许开发者设置目标对象和动画操纵的属性。在计算得到新的属性值时,这个类会相应地更新对象的属性。大多数时候,使用 ObjectAnimator 都是方便的,但有时候也需要使用 ValueAnimator ,因为 ObjectAnimator 有一些限制,例如 ObjectAnimator 需要目标类有相应的访问目标属性的方法 |
AnimatorSet |
AnimatorSet 提供了一种将动画合并到一起的机制,比如将多个动画同时播放、按顺序播放等。 |
Evaluators
用于告诉属性动画系统怎么计算属性值。它根据 Animator
提供的时间值(开始时间和结束时间),计算当前的属性值。SDK
提供以下 evaluators
。
Class/Interface | Description |
---|---|
IntEvaluator |
用于计算 int 类型的属性值 |
FloatEvaluator |
用于计算 Float 类型的属性值 |
AnimatorSet |
用于计算 ARGB 颜色类型的属性值 |
TypeEvaluator |
用于实现自定义属性值变化的 evaluator 。如果你正在作用的对象的属性不是 int 、float 、argb 类型的,必须实现 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
的使用方法如下所示:
// 表示将此按钮 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
位置的动画
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
对象时,需要时如下的代码:
ObjectAnimator.ofFloat(targetObject, "propName", 1f)
- 对于有些对象的属性,如果目标对象是
View
对象时,属性动画更新属性值的时候,需要调用invalidate()
方法强制地重新绘制这个View
对象。此步骤发生在onAnimationUpdate()
回调方法中。例如,若属性动画作用于一个Drawable
对象的颜色时,只有重新绘制该Drawable
对象,则颜色属性才会生效。对于View
对象的setAlpha()
、setTranslationX()
等属性值,在属性动画作用的时候,都会正确地重绘,所以不必关心它们。
4.3 ValueAnimator
ValueAnimator
可以在某一段时间内,对一些类型的值进行改变。通过调用它的 ofInt()
, ofFloat()
或 ofObject()
工厂方法可以得到 ValueAnimator
的对象。例如:
ValueAnimator animation = ValueAnimator.ofFloat(0f, 100f);
animation.setDuration(1000);
animation.start();
上述代码,当调用 start()
方法动画开始时,动画的值在 1000ms
内,从 0
变化到了 100
。
可以为 ValueAnimator
添加监听器,监测在这段时间内动画值的变化情况,如下代码所示:
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
,stretchAnim1
和stretchAnim2
- 接着播放
bounceBackAnim
- 最后播放
fadeAnim
代码如下所示:
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
按钮,在从小不断变大的过程中,透明度也在同时发生着变化,代码如下所示:
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
可以将一个动画拆分成多个阶段。如下代码所示:
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 动画监听器
在动画运行期间,可以通过以下监听器监听动画的关键时刻:
public static interface AnimatorListener {
// 在动画开始时被调用
void onAnimationStart(Animator animation);
// 在动画结束时被调用
void onAnimationEnd(Animator animation);
// 在动画取消时被调用
void onAnimationCancel(Animator animation);
// 在动画重复执行时被调用
void onAnimationRepeat(Animator animation);
}
public static interface AnimatorPauseListener {
//在动画暂停时被调用
void onAnimationPause(Animator animation);
// 在动画被重新执行时被调用
void onAnimationResume(Animator animation);
}
public static interface AnimatorUpdateListener {
// 在动画刷新每一帧的时候都会被调用
void onAnimationUpdate(ValueAnimator animation);
}
在
AnimatorUpdateListener.onAnimationUpdate(ValueAnimator animation)
方法中有animation
参数,通过参数animation.getAnimatedValue()
可以得到当前动画执行的完成度的值。如果不想实现
AnimatorListener
接口的所有方法,也可以继承AnimatorListenerAdapter
,选择想实现的方法。例如,以下代码通过实现AnimatorListenerAdapter
匿名内部类的方式,只实现了onAnimationEnd()
回调方法
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());
}
参考资料:
HenCoder Android 自定义 View 1-7:属性动画 Property Animation(进阶篇) -- HenCoder