本文介绍了Android中的属性动画的使用。
原文链接:Property Animation
属性动画在Android 3.0(API 11)引入,我们可以利用属性动画修改任何对象的属性值。
属性动画所支持的特性:
- Duration:动画持续时间,默认为300ms。
- Time interpolation:时间插值器,用来控制动画的速度。
- Repeat count and behavior:重复次数和行为,行为代表是否翻转动画。
- Animator sets:动画集合,可以同时播放或顺序播放或延时播放。
- Frame refresh delay:动画帧刷新的频率,默认为10ms。
属性动画的工作原理
下图说明了假设对一个对象的X属性产生动画,代表了它在屏幕上的水平位置。持续时间设置为40ms,移动的距离为40px。默认帧刷新频率为10ms,对象水平移动10px。这是线性插值器动画的一个例子,表示对象匀速移动。
图1 线性动画的示例
你也可以设置一个非线性插值器动画。下图说明了假设一个对象在动画开始加速,动画结束减速。对象仍然在40ms移动40px,但是是非线性的。
图2 非线性动画的示例
我们来详细看下属性动画是怎样计算的。
图3 动画计算流程
ValueAnimator会追踪动画的时间,例如动画运行了多长时间及属性的当前值。
ValueAnimator封装了TimeInterpolator用于定义动画插值器,和TypeEvaluator用于定义怎样计算属性值。例如在图2中, TimeInterpolator会是 AccelerateDecelerateInterpolator ,TypeEvaluator 会是 IntEvaluator。
为了产生动画,创建一个ValueAnimator并给你想要动画的开始和结束的属性值,和动画的持续时间。调用start()方法开始动画。动画持续期间,ValueAnimator会计算出从0到1运行的部分,基于动画的持续时间和运行的时间。运行的部分代表动画完成时间的百分比,0表示0%,1表示100%。例如,在图1中,运行的部分在 t = 10 ms时,将会是0.25,因为持续时间为t = 40 ms。
当ValueAnimator完成计算运行的部分,它会调用当前设置的TimeInterpolator,用于计算插值部分。插值部分映射运行部分到一个新的部分设置为时间插值。例如,在图2中,因为动画是慢加速,在t = 10 ms时,插值部分大约为0.15,比运行部分0.25少。在图1中,插值部分和运行部分相同。
当计算完插值部分,为了计算动画的属性值,ValueAnimator 会调用合适的 TypeEvaluator,基于插值部分,开始值和动画的结束值。例如,当t = 10 ms时,插值部分为0.15,因此属性值为0.15*(40-0)为6.
属性动画和View动画的区别
View动画只能作用于View对象,并且只能实现缩放、旋转、透明度、移动的动画效果。
View动画的另一个缺点是View动画并不能真正改变View的位置。
属性动画不存在View动画的限制,你可以对任何对象(Views and non-Views)的任何属性产生动画并且对象本身实际会被修改。属性动画也会用更健壮的方式进行动画。当熟练掌握属性动画的使用后,你可以为你想要产生动画的属性指定animators,例如,颜色,位置,或大小和可以定义动画的方面例如插值器和同步多个动画。
View动画花费更少的时间设置和编写。如果View动画可以完成你的需求,或者原有代码已经实现你想要的效果,就没必要使用属性动画了。这也会让两种动画系统用在不同的情况更有意义。
API概览
表1 Animators
Class | Description |
---|---|
ValueAnimator | 属性动画的主要的时间引擎也计算动画的属性值。它有计算动画值所有核心功能和包含每个动画的时间细节,动画是否重复的信息,接收更新事件的监听,和设置自定义评估器类型的能力。属性动画包含两部分:计算动画值和设置这些值到对象上,属性才会产生动画。 ValueAnimator 不能执行第二部分,因此你必须通过ValueAnimator 设置计算值更新的监听,然后用你自己的逻辑修改要产生动画的对象。 |
ObjectAnimator | 继承自 ValueAnimator 允许你设置一个目标对象和产生动画的对象属性。这个类会根据它为动画计算的新值更新属性。大多数情况下,我们会使用ObjectAnimator,因为它让在目标对象上的动画值的处理更容易。也有特殊情况,需要直接使用ValueAnimator,因为ObjectAnimator有几个限制,例如对目标对象需要特殊的访问方法。 |
AnimatorSet | 动画集合,可以设置动画同时播放,按顺序或指定延时。 |
评估器告诉属性动画应该怎么为所给属性计算值。
表2 Evaluators
Class/Interface | Description |
---|---|
IntEvaluator | 计算Int属性值的默认评估器 |
FloatEvaluator | 计算Float属性值的默认评估器 |
ArgbEvaluator | 计算Color属性值十六进制表示的默认评估器 |
TypeEvaluator | 一个接口,用于自定义评估器 |
下面为系统所提供的插值器Interpolators,通过实现TimeInterpolator接口可以自定义插值器。
- AccelerateDecelerateInterpolator
- AccelerateInterpolator
- AnticipateInterpolator
- AnticipateOvershootInterpolator
- BounceInterpolator
- CycleInterpolator
- DecelerateInterpolator
- LinearInterpolator
- OvershootInterpolator
- TimeInterpolator
使用ValueAnimator产生动画
通过ValueAnimator可以指定动画值的类型为int,float或color。通过调用它的ofInt(), ofFloat(), 或 ofObject()获取ValueAnimator。例如:1
2
3ValueAnimator animation = ValueAnimator.ofFloat(0f, 1f);
animation.setDuration(1000);
animation.start();
你也可以指定自定义类型:1
2
3ValueAnimator animation = ValueAnimator.ofObject(new MyTypeEvaluator(), startPropertyValue, endPropertyValue);
animation.setDuration(1000);
animation.start();
上面的代码片段不会有任何效果,因为ValueAnimator没有在对象或属性上操作。在下面的动画监听部分我们会详细介绍。
使用ObjectAnimator产生动画
实例化 ObjectAnimator和 ValueAnimator类似,但是还需要指定对象和对象的属性名称:1
2
3ObjectAnimator anim = ObjectAnimator.ofFloat(foo, "alpha", 0f, 1f);
anim.setDuration(1000);
anim.start();
为了ObjectAnimator可以正确更新属性,需要做下面的事情:
- 作用动画对象的属性必须有setter方法(驼峰式大小写)set
()的形式。因为在动画期间ObjectAnimator会自动更新属性,它必须通过这个setter方法访问属性。例如,如果属性名为foo,需要有一个setFoo()方法。如果这个setter方法不存在,你有三个选择: - 如果可以修改的话,添加一个setter方法。
- 如果可以修改的话,使用一个包装类用一个合法的setter方法接收值,传递到原始对象。
- 使用ValueAnimator代替
- 如果你只给ObjectAnimator的工厂方法的参数值只设置了一个值,那它将会是动画结束时的值。因此,要动画的对象属性必须还要提供getter方法用于获取动画开始值。getter方法必须是get
()形式。例如,属性名为foo,需要有一个getFoo()方法。 属性的getter和setter方法必须要和ObjectAnimator指定的开始和结束值的类型一致。如果要构造下面的ObjectAnimator必须要有targetObject.setPropName(float)和targetObject.getPropName(float):
1
ObjectAnimator.ofFloat(targetObject, "propName", 1f)
依赖于要动画的属性或对象,在View上你可能需要调用invalidate()方法使用更新的动画值强制屏幕重新绘制它。你可以在 onAnimationUpdate()回调方法中做处理。例如,对一个Drawable对象产生颜色属性动画,只会当对象重新绘制才会更新到屏幕上。View的所有属性setter例如 setAlpha() 方法内部会调用invalidate()方法,因此不需要自己处理。
动画集合
下面的代码来自于API demos中的Bouncing Balls 示例代码(做了简单的修改),动画的播放顺序为:
- 播放bounceAnim
- 同时播放squashAnim1, squashAnim2, stretchAnim1, 和 stretchAnim2
- 播放bounceBackAnim
- 播放fadeAnim
1 | AnimatorSet bouncer = new AnimatorSet(); |
调用play方法会返回一个AnimatorSet.Builder对象,AnimatorSet.Builder是一个实用类,用于帮助添加动画到集合中连同不同动画之间的关系。下面的代码表示当anim1结束同时开始anim2和anim3。anim2和anim3之间没有直接的关系:1
2AnimatorSet s = new AnimatorSet();
s.play(anim1).before(anim2).before(anim3);
如果你想要当anim1结束开始anim2,当anim2结束再开始anim3,下面的代码正确的表达了这样的关系:1
2
3AnimatorSet s = new AnimatorSet();
s.play(anim1).before(anim2);
s.play(anim2).before(anim3);
动画监听
Animator.AnimatorListener
- onAnimationStart() - 动画开始被调用
- onAnimationEnd() - 动画结束被调用
- onAnimationRepeat() - 动画重复被调用
- onAnimationCancel() - 动画取消被调用,这也会调用onAnimationEnd()方法。
ValueAnimator.AnimatorUpdateListener
- onAnimationUpdate() 动画更新的每一帧都会被调用。当需要在动画期间使用通过ValueAnimator计算生成的值时监听这个事件。通过getAnimatedValue()方法获取当前动画的值。
如果你不想实现 Animator.AnimatorListener接口的所有方法,你可以使用AnimatorListenerAdapter类。
例如,API demos中的Bouncing Balls示例代码创建了AnimatorListenerAdapter只重写了 onAnimationEnd()回调:1
2
3
4
5
6ValueAnimator fadeAnim = ObjectAnimator.ofFloat(newBall, "alpha", 1f, 0f);
fadeAnim.setDuration(250);
fadeAnim.addListener(new AnimatorListenerAdapter() {
public void onAnimationEnd(Animator animation) {
balls.remove(((ObjectAnimator)animation).getTarget());
}