7、View动画之布局动画:LayoutAnimationController、ViewGroup的animateLayoutChanges属性
布局动画,说白了就是当ViewGroup里的子View发生变化时,怎么让这个变化过程看起来更自然。我刚开始做Android开发那会儿,经常遇到列表刷新时子View突然出现或消失,那感觉就像PPT翻页一样生硬。后来接触了布局动画,才明白原来系统早就给我们准备好了解决方案。
7.1 布局动画的核心场景
你想想看,什么时候会用到布局动画?
- 子View的添加或删除——比如聊天列表来了新消息
- 子View的可见性变化——比如展开/收起某个面板
- 子View的位置重排——比如GridLayout里删掉一个item,后面的自动补位
这些场景如果没有任何动画,用户体验会非常突兀。我记得有一次做电商App的商品筛选面板,点击筛选条件后选项列表直接闪现,产品经理当场就说「这不行,太硬了」。嗯,那时候我就开始认真研究布局动画了。
7.2 LayoutAnimationController:给ViewGroup加动画
LayoutAnimationController是Android早期提供的布局动画方案。它可以在ViewGroup显示时,让子View按照一定的顺序和间隔依次播放入场动画。
7.2.1 基本用法
先看一个最简单的例子。假设我们有一个ListView,想让它的每个item从底部滑入:
// 创建动画
Animation slideIn = AnimationUtils.loadAnimation(this, R.anim.slide_in_bottom);
// 创建LayoutAnimationController
LayoutAnimationController controller = new LayoutAnimationController(slideIn);
controller.setDelay(0.3f); // 每个item延迟0.3倍动画时长
controller.setOrder(LayoutAnimationController.ORDER_NORMAL); // 顺序播放
// 设置给ListView
listView.setLayoutAnimation(controller);
对应的动画资源文件 res/anim/slide_in_bottom.xml:
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="300">
<translate
android:fromYDelta="100%p"
android:toYDelta="0" />
<alpha
android:fromAlpha="0.0"
android:toAlpha="1.0" />
</set>
7.2.2 三种播放顺序
LayoutAnimationController支持三种顺序模式,我整理了一个表格方便你对比:
| 常量 | 说明 | 适用场景 |
|---|---|---|
ORDER_NORMAL |
按子View的索引顺序播放 | 列表从上往下依次出现 |
ORDER_REVERSE |
按子View的索引倒序播放 | 从底部往上依次出现 |
ORDER_RANDOM |
随机顺序播放 | 营造活泼、随机的效果 |
我个人习惯在列表页使用ORDER_NORMAL,因为符合用户的阅读习惯。但在一些游戏或娱乐类App里,ORDER_RANDOM反而能带来惊喜感。
7.2.3 在XML中直接配置
除了代码设置,我们也可以在布局XML里直接配置:
<ListView
android:id="@+id/listView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layoutAnimation="@anim/layout_animation_controller" />
对应的 res/anim/layout_animation_controller.xml:
<?xml version="1.0" encoding="utf-8"?>
<layoutAnimation
xmlns:android="http://schemas.android.com/apk/res/android"
android:delay="0.3"
android:animation="@anim/slide_in_bottom"
android:animationOrder="normal" />
7.3 animateLayoutChanges属性:更智能的布局动画
LayoutAnimationController的局限性很明显——它只管「入场」,不管「变化」。那如果我想让子View在添加、删除、移动时都有动画呢?这时候就该animateLayoutChanges出场了。
7.3.1 一句话开启动画
在XML布局里加上这个属性:
<LinearLayout
android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:animateLayoutChanges="true"
android:orientation="vertical" />
就这么简单。当你对这个LinearLayout执行addView()、removeView()、setVisibility()等操作时,系统会自动加上淡入淡出、平移等过渡动画。
7.3.2 原理是什么?
其实animateLayoutChanges="true"背后是LayoutTransition在起作用。ViewGroup内部会注册一个View.OnLayoutChangeListener,当布局发生变化时,它会自动计算子View的旧位置和新位置,然后生成对应的动画。
我画了一张图来展示它的工作流程:
7.3.3 自定义LayoutTransition
如果你觉得默认的动画效果不够炫酷,也可以自定义:
LayoutTransition transition = new LayoutTransition();
// 自定义出现动画
ObjectAnimator appearAnim = ObjectAnimator.ofFloat(null, "alpha", 0f, 1f);
appearAnim.setDuration(500);
transition.setAnimator(LayoutTransition.APPEARING, appearAnim);
// 自定义消失动画
ObjectAnimator disappearAnim = ObjectAnimator.ofFloat(null, "alpha", 1f, 0f);
disappearAnim.setDuration(300);
transition.setAnimator(LayoutTransition.DISAPPEARING, disappearAnim);
// 设置给ViewGroup
container.setLayoutTransition(transition);
animateLayoutChanges="true"就够了。但如果你需要精细控制动画时长、插值器或动画类型,那就用LayoutTransition自定义。我在项目中通常先用默认属性快速验证效果,再根据UI设计稿微调。
7.4 两种方案的对比
我把LayoutAnimationController和animateLayoutChanges放在一起对比一下:
| 对比维度 | LayoutAnimationController | animateLayoutChanges |
|---|---|---|
| 触发时机 | ViewGroup首次显示 | 子View增删、可见性变化、位置变化 |
| 动画类型 | 仅入场动画 | 出现、消失、变化、移动 |
| 自定义程度 | 中等(可设置顺序、延迟) | 高(可自定义每种动画) |
| 适用场景 | 列表首次加载 | 动态交互、数据刷新 |
| API级别 | API 1+ | API 11+ |
7.5 避坑指南
最后分享几个我踩过的坑:
- 性能问题:如果ViewGroup里有大量子View(比如超过50个),
animateLayoutChanges可能会导致卡顿。我曾经在一个商品列表页用过,结果低端手机上动画掉帧严重。后来改成只对可见区域的前几个item做动画,后面的直接显示。 - 动画冲突:如果你同时使用了
animateLayoutChanges和自定义的LayoutAnimation,可能会出现动画叠加或冲突。建议只选一种方案。 - RecyclerView不适用:LayoutAnimationController对RecyclerView的支持有限,因为RecyclerView有自己的ItemAnimator机制。如果你在用RecyclerView,应该用
RecyclerView.ItemAnimator而不是布局动画。 - 动画被中断:如果用户在动画执行过程中快速操作,可能会导致动画状态错乱。我建议在动画执行期间禁用相关交互,或者使用
ViewPropertyAnimator.withEndAction()来确保状态同步。
嗯,布局动画这部分内容就到这里。记住一个原则:动画是为了提升用户体验,不是为了炫技。用得恰到好处才是高手。