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" />
注意:LayoutAnimationController只在ViewGroup首次显示时生效。如果你在运行时动态添加子View,它不会自动触发动画。这一点我踩过坑——当时以为设置一次就万事大吉了,结果数据刷新后新item直接冒出来,完全没有动画效果。

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的旧位置和新位置,然后生成对应的动画。

我画了一张图来展示它的工作流程:

animateLayoutChanges 工作流程 布局变化触发 addView / removeView LayoutTransition 拦截布局变化事件 计算新旧位置 生成动画参数 APPEARING 子View出现 DISAPPEARING 子View消失 CHANGING 子View自身变化 CHANGE_DISAPPEARING 其他View因删除而移动 ViewPropertyAnimator 执行动画 平移 + 透明度 + 缩放 系统自动完成,开发者只需设置 animateLayoutChanges="true"

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);
小技巧:如果你只想让某个ViewGroup有动画,又不想写太多代码,直接用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()来确保状态同步。

嗯,布局动画这部分内容就到这里。记住一个原则:动画是为了提升用户体验,不是为了炫技。用得恰到好处才是高手。


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