8、View的动画体系:View动画、属性动画与帧动画的区别,AnimatorSet的组合使用

动画,说白了就是让界面“动起来”。

Android 的动画体系,我把它分成三大流派:View 动画属性动画帧动画。很多人学了几年,还是搞不清什么时候该用哪个。今天咱们就把它们彻底掰开揉碎。

8.1 三大动画流派:各自安好,各司其职

先看一张图,快速建立整体认知:

Android 动画体系总览 View 动画(补间动画) 只改变绘制效果,不改变真实属性 属性动画 真正改变对象属性值 帧动画 逐帧播放图片序列 核心区别:View动画只改“画出来”的样子,属性动画改“真实的值” 适用场景 简单位移/旋转/缩放/透明 不需要交互反馈的场景 兼容低版本(API 1 就有) 需要改变真实位置/大小 需要交互点击跟随动画 自定义属性动画 复杂逐帧效果(如人物行走) GIF替代方案 注意内存!注意内存!

8.2 View 动画:老当益壮,但别滥用

View 动画也叫补间动画。它只改变 View 的绘制效果,不改变 View 的真实属性

什么意思?你让一个 Button 平移到右边,它确实显示在右边了。但你点击原来的位置,按钮依然响应点击。为什么?因为它的 lefttop 这些坐标根本没变。

核心特点:

  • 支持四种变换:平移、旋转、缩放、透明度
  • 通过 XML 或代码定义,使用简单
  • 动画结束后 View 会“弹回”原位(除非设置 fillAfter=true
  • 不触发 onLayout,性能较好

XML 示例:

<!-- res/anim/translate_anim.xml -->
<set xmlns:android="http://schemas.android.com/apk/res/android">
    <translate
        android:fromXDelta="0"
        android:toXDelta="200"
        android:duration="1000"
        android:fillAfter="true" />
</set>

代码启动:

Animation anim = AnimationUtils.loadAnimation(context, R.anim.translate_anim);
view.startAnimation(anim);

我曾经踩过的坑:

用 View 动画做了一个“滑动删除”效果。动画结束后,按钮虽然显示在屏幕外,但点击事件还在原位置。用户疯狂反馈“点不到删除按钮”。后来改成属性动画才解决。

所以记住:如果动画后需要交互,别用 View 动画

8.3 属性动画:真正的“动真格”

属性动画是 API 11 引入的。它直接修改对象的属性值,比如 translationXalpharotation 等。

说白了,它让 View 的“里子”和“面子”一起变。

基本用法:

ObjectAnimator animator = ObjectAnimator.ofFloat(view, "translationX", 0f, 300f);
animator.setDuration(1000);
animator.start();

这里有个细节:translationX 是 View 的属性,必须有 setTranslationX() 方法。属性动画就是通过反射调用 setter 来实现的。

我的习惯:

能用属性动画,尽量不用 View 动画。尤其是做列表 item 动画、拖拽效果、手势跟随动画时,属性动画是唯一正确的选择。

8.4 帧动画:简单粗暴,但小心内存

帧动画就是逐帧播放图片。像放幻灯片一样,一张一张切换。

XML 定义:

<!-- res/drawable/frame_anim.xml -->
<animation-list xmlns:android="http://schemas.android.com/apk/res/android"
    android:oneshot="false">
    <item android:drawable="@drawable/frame_1" android:duration="100" />
    <item android:drawable="@drawable/frame_2" android:duration="100" />
    <item android:drawable="@drawable/frame_3" android:duration="100" />
</animation-list>

代码启动:

ImageView imageView = findViewById(R.id.iv_frame);
imageView.setImageResource(R.drawable.frame_anim);
AnimationDrawable anim = (AnimationDrawable) imageView.getDrawable();
anim.start();

注意:

帧动画会把所有图片加载到内存。如果帧数多、图片大,很容易 OOM。我见过一个项目,用 60 帧的帧动画做加载效果,结果低端机直接闪退。

建议:帧数控制在 10 帧以内,图片用 WebP 格式压缩。

8.5 AnimatorSet:让动画“组队”干活

实际项目中,很少只做一个动画。往往是多个动画配合:先放大、再旋转、同时透明度变化……

AnimatorSet 就是用来编排这些动画的。

核心方法:

方法 作用
play(Animator) 指定一个动画作为“基准”
with(Animator) 与基准动画同时播放
before(Animator) 在基准动画之前播放
after(Animator) 在基准动画之后播放
after(long delay) 延迟指定时间后播放

实战示例:

ObjectAnimator scaleX = ObjectAnimator.ofFloat(view, "scaleX", 1f, 1.5f);
ObjectAnimator scaleY = ObjectAnimator.ofFloat(view, "scaleY", 1f, 1.5f);
ObjectAnimator alpha = ObjectAnimator.ofFloat(view, "alpha", 1f, 0.5f);
ObjectAnimator translation = ObjectAnimator.ofFloat(view, "translationY", 0f, -200f);

AnimatorSet set = new AnimatorSet();
// 先放大 + 变透明(同时),然后向上移动
set.play(scaleX).with(scaleY).with(alpha);
set.play(translation).after(scaleX);
set.setDuration(800);
set.start();

这段代码的效果是:View 先放大并变透明,然后向上弹起。很像一个“消失”的动效。

避坑指南:

我曾经用 AnimatorSet 做复杂的入场动画,发现动画顺序总是乱。后来排查发现,play().before()play().after() 的调用顺序会影响结果。

建议:先确定一个“锚点”动画,其他动画都基于它来编排。不要交叉引用,容易逻辑混乱。

8.6 如何选择?一张表说清楚

场景 推荐方案 原因
简单位移/旋转/缩放(无需交互) View 动画 代码少,性能好,兼容性好
需要点击跟随动画 属性动画 真实改变位置,点击区域同步
自定义属性动画(如背景色渐变) 属性动画 + ValueAnimator 可以动画任意属性
复杂逐帧效果(人物行走、加载动画) 帧动画 简单直接,但注意内存
多个动画组合,有先后顺序 AnimatorSet 编排灵活,支持并行/串行
需要循环播放的简单动画 View 动画 + repeatCount 一行代码搞定循环

嗯,到这里,三大动画流派和它们的组合用法就讲清楚了。你想想看,是不是比之前清晰多了?

下次写动画时,先问自己三个问题:

  1. 动画后需要交互吗?→ 需要就用属性动画
  2. 动画复杂吗?→ 复杂就用 AnimatorSet 编排
  3. 内存够吗?→ 帧动画慎用大图

最后说一句:

我个人习惯在项目里建一个 AnimUtils 工具类,把常用的动画组合封装成静态方法。比如 AnimUtils.fadeOutAndMoveUp(view),这样团队里所有人都能复用,也方便统一修改动画参数。


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