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里暂停动画的习惯。

知识体系梳理

为了让你更直观地理解这三块内容的关系,我画了一张图:

属性动画进阶三大核心 Keyframe动画 多段变化控制 • 定义时间点+属性值 • 组合PropertyValuesHolder • 支持float/int/color • 适合复杂曲线动画 • 卡片翻转场景 • 点赞弹跳效果 ViewPropertyAnimator 链式调用快捷方式 • view.animate() 入口 • 链式调用一行搞定 • 内部单循环优化 • 仅支持View对象 • 常用属性全覆盖 • 列表入场动画首选 暂停与恢复 生命周期管理 • pause() / resume() • API 19+ 原生支持 • onPause中暂停 • onResume中恢复 • 配合LifecycleObserver • 避免View跑飞 三者互补:Keyframe控制复杂路径,ViewPropertyAnimator简化日常,暂停恢复保证体验

实际项目中的选择建议

说了这么多,到底什么时候用哪个?我整理了一个表格,方便你对照:

场景 推荐方案 理由
简单的平移/缩放/透明度 ViewPropertyAnimator 代码最少,性能最优
多段变化的复杂动画 Keyframe + PropertyValuesHolder 一个动画搞定,不用串多个
操作非View对象的属性 ObjectAnimator ViewPropertyAnimator不支持
需要暂停/恢复的动画 Animator.pause()/resume() 原生支持,稳定可靠
列表item入场动画 ViewPropertyAnimator 链式调用,配合延迟效果很好

我的个人习惯:能用ViewPropertyAnimator的绝不用ObjectAnimator。只有遇到多段变化或者自定义属性时,才掏出Keyframe。至于暂停恢复,每个动画我都会加上生命周期管理,哪怕只是一个200ms的短动画——因为你永远不知道用户什么时候会接电话。

好了,这一节的内容就到这里。属性动画的进阶用法,说白了就是让你更精细地控制动画的每一帧、更优雅地写代码、更安全地管理动画生命周期。下一节我们会聊聊动画的监听器和回调机制,到时候再细说。

公众号:蓝海资料掘金营,微信deep3321