10、View动画之属性动画进阶:Keyframe动画、ViewPropertyAnimator、动画的暂停与恢复
属性动画的基础用法,说白了就是改变一个对象的某个属性值。但实际项目中,光会基础可不够。你想想看,一个点赞动画,先放大再缩小,最后弹一下——这种多段变化怎么搞?动画跑到一半,用户划走了,要不要停?
嗯,今天我们就来聊聊这些进阶玩法。我个人在好几个项目里都踩过这些坑,尤其是动画暂停恢复那块,曾经让我排查了一整个下午。
Keyframe动画:精准控制每一帧
先说说Keyframe。它是什么?说白了,就是让你在动画的某个时间点,指定一个具体的属性值。比如一个View从0旋转到360度,中间你想让它先转到180度,再退回到90度,最后再冲到360度——用普通的ValueAnimator你得写好几段动画串起来,但用Keyframe,一个动画就搞定。
核心思路:Keyframe定义的是「时间点 + 属性值」的映射关系。多个Keyframe组合成一个PropertyValuesHolder,再交给ObjectAnimator去跑。
来看代码:
// 定义三个关键帧
Keyframe kf0 = Keyframe.ofFloat(0f, 0f); // 0% 时,旋转角度为0
Keyframe kf1 = Keyframe.ofFloat(0.5f, 180f); // 50%时,旋转到180度
Keyframe kf2 = Keyframe.ofFloat(1f, 360f); // 100%时,旋转到360度
// 组合成PropertyValuesHolder
PropertyValuesHolder pvhRotation =
PropertyValuesHolder.ofKeyframe("rotation", kf0, kf1, kf2);
// 执行动画
ObjectAnimator animator = ObjectAnimator.ofPropertyValuesHolder(view, pvhRotation);
animator.setDuration(2000);
animator.start();
我在项目中遇到过一种场景:一个卡片翻转效果,前半段是Y轴旋转到90度(几乎看不见),后半段从90度继续转到180度。用Keyframe就特别优雅,中间那个90度的关键帧,正好用来切换卡片正反面内容。
小技巧:Keyframe不光支持float,还支持int、color、甚至自定义类型。如果你需要做颜色渐变中间插一个跳变点,用Keyframe.ofInt()配合ArgbEvaluator,效果很丝滑。
ViewPropertyAnimator:链式调用的优雅写法
接下来聊聊ViewPropertyAnimator。这个东西,我个人非常喜欢。它其实就是View类自带的一个动画快捷方式。你想想看,如果只是让一个View平移、缩放、透明度变化,用ObjectAnimator你得写好几行,但用ViewPropertyAnimator,一行链式调用就搞定。
view.animate()
.translationX(300f)
.scaleX(1.5f)
.scaleY(1.5f)
.alpha(0.5f)
.setDuration(500)
.setInterpolator(new AccelerateDecelerateInterpolator())
.start();
注意,最后那个.start()其实可以省略。因为animate()返回的是一个ViewPropertyAnimator对象,它会在下一帧自动启动。但我建议你还是显式写出来,代码可读性更好。
为什么说它好用?因为它内部做了优化。多个属性同时变化时,它只会启动一个动画循环,而不是每个属性单独开一个动画。性能上比多个ObjectAnimator同时跑要好一些。
注意:ViewPropertyAnimator只能操作View,不能操作自定义对象。而且它只支持几个常用属性:translationX/Y、rotation、scaleX/Y、alpha等。如果你需要操作自定义属性,还是得用ObjectAnimator。
我曾经在一个列表的入场动画里,用ViewPropertyAnimator给每个item做淡入+上移。代码写起来特别清爽,而且因为内部做了优化,60帧稳稳的。
动画的暂停与恢复
这个功能,Android在API 19(Android 4.4)才正式支持。在那之前,想暂停动画?只能自己用ValueAnimator的setCurrentPlayTime()去模拟。嗯,我早期项目里就干过这种事,代码写得又丑又容易出bug。
现在简单了,直接调用:
// 暂停
animator.pause();
// 恢复
animator.resume();
但这里有个坑——pause()和resume()只对Animator的子类有效。如果你用的是Animation(补间动画),那对不起,没有这两个方法。所以我的建议是:新项目一律用属性动画,别再用Animation了。
还有一个更隐蔽的问题:当Activity进入后台时,系统会调用onPause(),但不会自动暂停你的动画。如果你不做处理,动画会继续跑,等用户切回来,View可能已经跑到奇怪的位置了。
最佳实践:在Activity的onPause()里暂停所有动画,在onResume()里恢复。配合LifecycleObserver使用更优雅。
@Override
protected void onPause() {
super.onPause();
if (animator != null && animator.isRunning()) {
animator.pause();
}
}
@Override
protected void onResume() {
super.onResume();
if (animator != null && animator.isPaused()) {
animator.resume();
}
}
我曾经在一个直播项目里,因为没处理这个,导致用户切出去再回来,点赞动画的图标飞到了屏幕外面。嗯,从那以后,我养成了在onPause里暂停动画的习惯。
知识体系梳理
为了让你更直观地理解这三块内容的关系,我画了一张图:
实际项目中的选择建议
说了这么多,到底什么时候用哪个?我整理了一个表格,方便你对照:
| 场景 | 推荐方案 | 理由 |
|---|---|---|
| 简单的平移/缩放/透明度 | ViewPropertyAnimator | 代码最少,性能最优 |
| 多段变化的复杂动画 | Keyframe + PropertyValuesHolder | 一个动画搞定,不用串多个 |
| 操作非View对象的属性 | ObjectAnimator | ViewPropertyAnimator不支持 |
| 需要暂停/恢复的动画 | Animator.pause()/resume() | 原生支持,稳定可靠 |
| 列表item入场动画 | ViewPropertyAnimator | 链式调用,配合延迟效果很好 |
我的个人习惯:能用ViewPropertyAnimator的绝不用ObjectAnimator。只有遇到多段变化或者自定义属性时,才掏出Keyframe。至于暂停恢复,每个动画我都会加上生命周期管理,哪怕只是一个200ms的短动画——因为你永远不知道用户什么时候会接电话。
好了,这一节的内容就到这里。属性动画的进阶用法,说白了就是让你更精细地控制动画的每一帧、更优雅地写代码、更安全地管理动画生命周期。下一节我们会聊聊动画的监听器和回调机制,到时候再细说。