8、View的动画体系:View动画、属性动画与帧动画的区别,AnimatorSet的组合使用
动画,说白了就是让界面“动起来”。
Android 的动画体系,我把它分成三大流派:View 动画、属性动画、帧动画。很多人学了几年,还是搞不清什么时候该用哪个。今天咱们就把它们彻底掰开揉碎。
8.1 三大动画流派:各自安好,各司其职
先看一张图,快速建立整体认知:
8.2 View 动画:老当益壮,但别滥用
View 动画也叫补间动画。它只改变 View 的绘制效果,不改变 View 的真实属性。
什么意思?你让一个 Button 平移到右边,它确实显示在右边了。但你点击原来的位置,按钮依然响应点击。为什么?因为它的 left、top 这些坐标根本没变。
核心特点:
- 支持四种变换:平移、旋转、缩放、透明度
- 通过 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 引入的。它直接修改对象的属性值,比如 translationX、alpha、rotation 等。
说白了,它让 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 | 一行代码搞定循环 |
嗯,到这里,三大动画流派和它们的组合用法就讲清楚了。你想想看,是不是比之前清晰多了?
下次写动画时,先问自己三个问题:
- 动画后需要交互吗?→ 需要就用属性动画
- 动画复杂吗?→ 复杂就用 AnimatorSet 编排
- 内存够吗?→ 帧动画慎用大图
最后说一句:
我个人习惯在项目里建一个 AnimUtils 工具类,把常用的动画组合封装成静态方法。比如 AnimUtils.fadeOutAndMoveUp(view),这样团队里所有人都能复用,也方便统一修改动画参数。
公众号:蓝海资料掘金营,微信deep3321