前言
在早期,Android 提供了逐帧动画(Frame Animation)和补间动画(Tween Animation)两种动画方式,这两种动画方式能够满足大部分基础动画需求。然而,随着开发需求的不断变化,Android 于 3.0 版本推出了属性动画,成为更强大、更灵活的动画框架,并沿用至今。
在进行属性动画的开发中, ObjectAnimator
是最常用到的类,它可以直接对任意对象的任意属性进行动画操作。不过虽说 ObjectAnimator
更加常用,但是它其实是继承自 ValueAnimator
的,底层的动画实现机制也是基于 ValueAnimator
来完成的,因此 ValueAnimator
仍然是整个属性动画中最核心的一个类。
所以这篇文章就来讲讲 ValueAnimator
这个类的使用,以及它有哪些需要注意的坑。
ValueAnimator
基础用法
属性动画的运行机制是不断地对值进行修改来实现的,而这个值从开始到结束之间如何修改就是由 ValueAnimator
这个类来计算的。我们只需要将初始值和结束值提供给 ValueAnimator
,并且告诉它动画运行的时长,ValueAnimator
就会自动帮我们完成从初始值到结束值的修改。除此之外,ValueAnimator
还可以设置动画的重复次数,重复时的播放模式,以及各种监听等。
虽然 ValueAnimator
是一个非常重要的类,但是它的使用却一点都不复杂。下面是一个 ValueAnimator
的简单例子:
ValueAnimator valueAnimator = ValueAnimator.ofFloat(1F);
valueAnimator.setDuration(500);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
final float currentValue = (float) animation.getAnimatedValue();
Log.d("ValueAnimation", "ValueAnimator.onAnimationUpdate currentValue : "+currentValue);
}
});
valueAnimator.addListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animation) {
Log.d("ValueAnimation", "ValueAnimator.onAnimationStart");
}
@Override
public void onAnimationEnd(Animator animation) {
Log.d("ValueAnimation", "ValueAnimator.onAnimationEnd");
}
@Override
public void onAnimationCancel(Animator animation) {
Log.d("ValueAnimation", "ValueAnimator.onAnimationCancel");
}
@Override
public void onAnimationRepeat(Animator animation) {
Log.d("ValueAnimation", "ValueAnimator.onAnimationRepeat");
}
});
valueAnimator.start();
这个例子使用 ValueAnimator
创建了一个值从 0F 到 1F 之间的变化(ValueAnimator.ofFloat(1F)
),
变化的总时长是 500 毫秒(setDuration(500)
),
添加了值变化时的监听(addUpdateListener(AnimatorUpdateListener listener)
),
添加了动画状态的监听(addListener(AnimatorListener listener)
),
最后使用 start()
方法让这个动画运行起来。
在这些监听里,现在都是打印了 log,方便查看整个动画运行的过程。如下:
2024-12-07 18:35:12.621 D ValueAnimator.onAnimationStart
2024-12-07 18:35:12.621 D ValueAnimator.onAnimationUpdate currentValue : 0.0
2024-12-07 18:35:12.674 D ValueAnimator.onAnimationUpdate currentValue : 0.0
2024-12-07 18:35:13.064 D ValueAnimator.onAnimationUpdate currentValue : 0.8729706
...
2024-12-07 18:35:13.140 D ValueAnimator.onAnimationUpdate currentValue : 0.9892905
2024-12-07 18:35:13.188 D ValueAnimator.onAnimationUpdate currentValue : 1.0
2024-12-07 18:35:13.192 D ValueAnimator.onAnimationEnd
以上是 ValueAnimator
的基本用法,不过大家有没有发现,onAnimationUpdate
在初始值 0F 时,被回调了两次。这就是我们要说的 ValueAnimator
的第一个个坑。
坑一:onAnimationUpdate
在初始值时被回调两次
从上面的 log 能看出确实发现了这个问题,那我们换一下,看看其他类型的 ValueAnimator
是不是也有这个问题:
ValueAnimator valueAnimator = ValueAnimator.ofInt(50);
打印出来的 log 如下:
2024-12-07 19:40:56.132 D ValueAnimator.onAnimationStart
2024-12-07 19:40:56.133 D ValueAnimator.onAnimationUpdate currentValue : 0
2024-12-07 19:40:56.174 D ValueAnimator.onAnimationUpdate currentValue : 0
2024-12-07 19:40:56.345 D ValueAnimator.onAnimationUpdate currentValue : 12
2024-12-07 19:40:56.356 D ValueAnimator.onAnimationUpdate currentValue : 14
2024-12-07 19:40:56.368 D ValueAnimator.onAnimationUpdate currentValue : 17
......
2024-12-07 19:40:56.652 D ValueAnimator.onAnimationUpdate currentValue : 49
2024-12-07 19:40:56.668 D ValueAnimator.onAnimationUpdate currentValue : 50
2024-12-07 19:40:56.669 D ValueAnimator.onAnimationEnd
可以看到 onAnimationUpdate
方法在 0 这个初始值上确实回调了两次。也就意味着 ValueAnimator
确实存在此问题,在编码时需要注意这个坑。
这里我们不继续深入为什么会有这个坑,只考虑如何避免这个坑带来的问题。
有些程序员会在初始值时做一些动画的准备工作,创建必要的对象,读取动画资源,为变量赋初始值等操作:
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
final int currentValue = (int) animation.getAnimatedValue();
if(currentValue == 0) {
//动画的初始化工作,资源准备
}
//动画逻辑
Log.d("ValueAnimation", "ValueAnimator.onAnimationUpdate currentValue : "+currentValue);
}
});
在没有这个坑的情况下,这是没问题的操作。但在这个坑的情况下,onAnimationUpdate
会在初始值调用两次,也就意味着会进行两此动画的初始化工作,这就会造成逻辑错误了。
所以,一般情况下,在添加 ValueAnimator.AnimatorUpdateListener
时,我们一般这么添加:
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
private int lastValue = -1 ;
@Override
public void onAnimationUpdate(ValueAnimator animation) {
final int currentValue = (int) animation.getAnimatedValue();
if(lastValue == currentValue) {
return;
}
lastValue = currentValue;
if(currentValue == 0) {
//动画的初始化工作,资源准备
}
//动画逻辑
Log.d("ValueAnimation", "ValueAnimator.onAnimationUpdate currentValue : "+currentValue);
}
});
这么做有两个好处:
- 避免了
onAnimationUpdate
在初始值调用两次的坑带来的各种问题; - 避免了
onAnimationUpdate
回调频率过高带来的性能问题,只在值发生变化时进行必要的操作;
在看完了 ValueAnimator
的基础用法和它的第一个坑之后,我们再来看看它的其他的用法和另外的坑。
ValueAnimator
设置重复播放
最前面说到,ValueAnimator
可以设置动画循环播放,这里涉及到的方法有两个:
ValueAnimator.setRepeatCount(int value)
;设置重复播放的次数ValueAnimator.setRepeatMode(@RepeatMode int value)
;设置动画重复播放的模式
其中 setRepeatCount
可以设置 0 表示不重复(这也是默认值,也就是只播放一次,无重复),也可以设置任意大于0 的值表示动画重复的次数,还能设置为 ValueAnimator.INFINITE
表示动画将无限循环播放;
而 setRepeatMode(@RepeatMode int value)
方法可以传入的参数为 ValueAnimator.RESTART
表示播放到结束值后,跳转到初始值再不断更新到结束值,ValueAnimator.REVERSE
则表示播放到结束值之后,将逆转播放顺序,下次播放就从结束值不断更新到开始值,然后不断逆转。但是无论是设置什么值,也只有在重复播放时才会看到效果。
我们可以将代码做如下修改:
ValueAnimator valueAnimator = ValueAnimator.ofInt(1, 50);
valueAnimator.setDuration(500);
valueAnimator.setRepeatCount(2);
valueAnimator.setRepeatMode(ValueAnimator.RESTART);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
final int currentValue = (int) animation.getAnimatedValue();
Log.d("ValueAnimation", "ValueAnimator.onAnimationUpdate currentValue : "+currentValue);
}
});
valueAnimator.addListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animation) {
Log.d("ValueAnimation", "ValueAnimator.onAnimationStart");
}
@Override
public void onAnimationEnd(Animator animation) {
Log.d("ValueAnimation", "ValueAnimator.onAnimationEnd");
}
@Override
public void onAnimationCancel(Animator animation) {
Log.d("ValueAnimation", "ValueAnimator.onAnimationCancel");
}
@Override
public void onAnimationRepeat(Animator animation) {
Log.d("ValueAnimation", "ValueAnimator.onAnimationRepeat");
}
});
valueAnimator.start();
将上述代码运行后,能得到如下的 log:
2024-12-07 21:49:25.634 D ValueAnimator.onAnimationStart
2024-12-07 21:49:25.634 D ValueAnimator.onAnimationUpdate currentValue : 1
2024-12-07 21:49:26.005 D ValueAnimator.onAnimationUpdate currentValue : 1
2024-12-07 21:49:26.022 D ValueAnimator.onAnimationUpdate currentValue : 12
...... 1 --> 50
2024-12-07 21:49:26.176 D ValueAnimator.onAnimationUpdate currentValue : 47
2024-12-07 21:49:26.196 D ValueAnimator.onAnimationUpdate currentValue : 49
2024-12-07 21:49:26.236 D ValueAnimator.onAnimationRepeat
2024-12-07 21:49:26.237 D ValueAnimator.onAnimationUpdate currentValue : 50
2024-12-07 21:49:26.257 D ValueAnimator.onAnimationUpdate currentValue : 1
2024-12-07 21:49:26.313 D ValueAnimator.onAnimationUpdate currentValue : 1
2024-12-07 21:49:26.313 D ValueAnimator.onAnimationUpdate currentValue : 2
...... 1 --> 50
2024-12-07 21:49:26.682 D ValueAnimator.onAnimationUpdate currentValue : 48
2024-12-07 21:49:26.705 D ValueAnimator.onAnimationUpdate currentValue : 49
2024-12-07 21:49:26.735 D ValueAnimator.onAnimationRepeat
2024-12-07 21:49:26.783 D ValueAnimator.onAnimationUpdate currentValue : 50
2024-12-07 21:49:26.799 D ValueAnimator.onAnimationUpdate currentValue : 1
2024-12-07 21:49:26.799 D ValueAnimator.onAnimationUpdate currentValue : 1
2024-12-07 21:49:26.799 D ValueAnimator.onAnimationUpdate currentValue : 2
...... 1 --> 50
2024-12-07 21:49:27.199 D ValueAnimator.onAnimationUpdate currentValue : 49
2024-12-07 21:49:27.232 D ValueAnimator.onAnimationUpdate currentValue : 50
2024-12-07 21:49:27.233 D ValueAnimator.onAnimationEnd
通过 log 能看到因为设置了重复次数为 2,因此动画总共运行了 3 次,又因为设置了重复的模式为 RESTART
,所以值的变化从 1 --> 50 重复 3 次。可见这两个方法都是可以正常调用的。
但是眼尖的同学应该在这些 log 中发现了坑。
坑二:onAnimationRepeat
的调用时机不太对
在 Log 中,我们能看出来,第一个坑仍然存在,而且我们发现了新的坑,那就是 onAnimationRepeat
这个回调的调用时机不太对。
在常人的理解中,起码在我的理解中,当我看到这个方法,我会认为这个方法应该在两次动画播放之间进行回调,即:
- onAnimationUpdate 0 --> 50
- onAnimationRepeat
- onAnimationUpdate 0 --> 50
然而,实际情况却不是这样:onAnimationRepeat
会在每一次动画的最后一帧之前被调用。
这种奇怪的调用时机我不理解,也确实大受震撼。一般情况下,在需要指定动画重复次数的场景下,也需要获取当前是第几次播放,以便在 onAnimationUpdate
有播放不同动画的可能性。但是这种坑的存在,使得处理这种问题只能在 onAnimationUpdate
回调中做特殊处理,这个坑也似乎使 onAnimationRepeat
这个回调丧失了意义。但也有可能 Google 觉得这么设计才是对的。
作为开发者,在使用 ValueAnimator
的时候,一定要注意这个坑,在各自的场景下尽量避免回调时机带来的逻辑问题。
但是你以为这两个坑就完了么?不,还有,但介绍三个坑之前,我们来介绍属性动画中常用的类:AnimationSet
。
AnimatorSet
组合多个动画
AnimationSet
是 Android 中用于组合多个动画的容器类。通过 AnimationSet
,你可以将多个独立的动画组合成一个整体,使得它们可以同时播放或是顺序播放,从而创建更复杂的动画效果。
AnimationSet
是 Animation
的子类,其本身也是一种动画,因此你可以将一个 AnimationSet
放到另一个 AnimationSet
中。其常用方法与 ValueAnimator
类似,但是有两个方法需要注意:
AnimationSet.playSequentially(Animator... items)
将传入的动画按照顺序播放AnimationSet.playTogether(Animator... items)
将传入的动画一起播放
当然你也可以通过 AnimatorSet.Builder
的四个方法做更方便的设置。
无论是调用 playSequentially
还是 playTogether
,AnimatorSet
都需要调用 start()
方法才能开启动画,也可以为其设置与 ValueAnimator
相同的监听。
我们看下面的代码:
ValueAnimator valueAnimator_1 = ValueAnimator.ofInt(1, 50);
ValueAnimator valueAnimator_2 = ValueAnimator.ofInt(100, 150);
AnimatorSet animatorSet = new AnimatorSet();
animatorSet.playSequentially(valueAnimator_1,valueAnimator_2);
animatorSet.addListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animation) {
Log.d("ValueAnimation", "animatorSet.onAnimationStart");
}
@Override
public void onAnimationEnd(Animator animation) {
Log.d("ValueAnimation", "animatorSet.onAnimationEnd");
}
@Override
public void onAnimationCancel(Animator animation) {
Log.d("ValueAnimation", "animatorSet.onAnimationCancel");
}
@Override
public void onAnimationRepeat(Animator animation) {
Log.d("ValueAnimation", "animatorSet.onAnimationRepeat");
}
});
animatorSet.start();
这段代码简单演示了 AnimatorSet
的使用,使用它来播放两个 ValueAnimator
,一个是从 0 到 50,一个是从 100 到 150。
我们再看一下 log:
2024-12-07 23:26:52.249 D valueAnimator_1.onAnimationStart
2024-12-07 23:26:52.250 D valueAnimator_1.onAnimationUpdate currentValue : 1
2024-12-07 23:26:52.250 D valueAnimator_1.onAnimationUpdate currentValue : 1
2024-12-07 23:26:52.250 D animatorSet.onAnimationStart
2024-12-07 23:26:52.317 D valueAnimator_1.onAnimationUpdate currentValue : 1
2024-12-07 23:26:52.567 D valueAnimator_1.onAnimationUpdate currentValue : 25
...... 1 --> 50
2024-12-07 23:26:52.820 D valueAnimator_1.onAnimationUpdate currentValue : 49
2024-12-07 23:26:52.840 D valueAnimator_1.onAnimationUpdate currentValue : 50
2024-12-07 23:26:52.840 D valueAnimator_1.onAnimationEnd
2024-12-07 23:26:52.841 D valueAnimator_2.onAnimationStart
2024-12-07 23:26:52.841 D valueAnimator_2.onAnimationUpdate currentValue : 100
2024-12-07 23:26:52.846 D valueAnimator_2.onAnimationUpdate currentValue : 100
2024-12-07 23:26:52.846 D valueAnimator_2.onAnimationUpdate currentValue : 100
2024-12-07 23:26:52.873 D valueAnimator_2.onAnimationUpdate currentValue : 101
...... 100 --> 150
2024-12-07 23:26:53.300 D valueAnimator_2.onAnimationUpdate currentValue : 149
2024-12-07 23:26:53.319 D valueAnimator_2.onAnimationUpdate currentValue : 150
2024-12-07 23:26:53.320 D valueAnimator_2.onAnimationEnd
2024-12-07 23:26:53.320 D animatorSet.onAnimationEnd
能看到两个 ValueAnimator
都按照顺序播放了,并且 AnimatorSet
的回调都被调用了。使用 AnimatorSet
就是这么简单。
不过咱们现在把目光放到 log 的最前面的一部分,就会发现第三个坑所在了。
坑三:ValueAnimator
先于 AnimatorSet
的 onAnimationStart
方法执行
理论上来讲,当使用 AnimatorSet
包装了两个 ValueAnimator
之后,应该是 AnimatorSet
的 onAnimationStart
方法先执行,然后开始两个 ValueAnimator
的播放。但实际上,会将第一个动画先进行调用,而且会回调 onAnimationUpdate
:
2024-12-07 23:26:52.249 D valueAnimator_1.onAnimationStart
2024-12-07 23:26:52.250 D valueAnimator_1.onAnimationUpdate currentValue : 1
2024-12-07 23:26:52.250 D valueAnimator_1.onAnimationUpdate currentValue : 1
2024-12-07 23:26:52.250 D animatorSet.onAnimationStart
......
一般来说,大家在使用动画时,都需要在 onAnimationStart
中进行一些初始化,创建一些对象,而这个坑的存在直接使得在 AnimatorSet
的 onAnimationStart
方法中进行必要的初始化这一操作变得不可能。
那么怎么避免这个问题呢?只能将初始化操作放到各个 ValueAnimator
的 onAnimationStart
方法中了。
简单总结一下,就是 ValueAnimator.onAnimationRepeat
和 AnimatorSet.onAnimationStart
这两个回调不可靠,别使用就行了。
另外,大家在设置 AnimatorListener
的时候,总是要覆写一些可能用不上的方法,这里推荐 AnimatorListenerAdapter
,使用它可以让你只关注自己需要的方法。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。