Jetpack Compose 布局与可见性动画

简介

在第一部分中,我们掌握了 animate*AsState 的声明式简洁性。但当 UI 的结构发生变化时------比如一个组件突然消失,或者容器的大小需要调整------简单的属性动画就不够用了。

本章我们将深入探讨布局动画。这些工具能让你的 UI 在组件出现、消失或调整大小时,表现得像水流一样顺滑且富有逻辑。


1. 进入与退出:AnimatedVisibility

直接使用 if (visible) 切换组件会导致 UI "瞬间弹出或消失",这在视觉上非常生硬。AnimatedVisibility 是 Compose 专门为这种"存在性过渡"设计的容器。

核心机制:

  • 状态驱动 :基于 visible: Boolean 自动触发。
  • 布局感知 :当组件退出动画结束后,它会自动从组合树(Composition Tree)中移除,不再占用布局空间

示例:平滑通知栏

Kotlin 复制代码
@Composable
fun AnimatedNotification(isVisible: Boolean, message: String) {
    AnimatedVisibility(
        visible = isVisible,
        // 组合动画:滑入 + 淡入
        enter = slideInVertically(initialOffsetY = { -it }) + fadeIn(initialAlpha = 0.3f),
        // 组合动画:滑出 + 淡出
        exit = slideOutVertically(targetOffsetY = { -it }) + fadeOut()
    ) {
        Surface(
            modifier = Modifier.fillMaxWidth(),
            color = MaterialTheme.colors.error,
            elevation = 4.dp
        ) {
            Text(text = message, modifier = Modifier.padding(16.dp), color = Color.White)
        }
    }
}

2. 容器大小的魔法:Modifier.animateContentSize()

当卡片内的文本从"摘要"变为"全文"时,高度的剧烈跳变会干扰用户。animateContentSize() 告诉布局系统:"当我的子组件改变我的大小时,请通过动画平滑过渡,不要跳变。"

场景:可展开的详情卡片

Kotlin 复制代码
@Composable
fun ExpandableCard(text: String) {
    var expanded by remember { mutableStateOf(false) }

    Card(
        modifier = Modifier
            .fillMaxWidth()
            .padding(8.dp)
            // 核心:当内部内容变化时,自动动画化 Card 的大小
            .animateContentSize(animationSpec = spring(Spring.DampingRatioLowBouncy))
            .clickable { expanded = !expanded }
    ) {
        Column(Modifier.padding(16.dp)) {
            Text(
                text = text,
                maxLines = if (expanded) Int.MAX_VALUE else 2,
                overflow = TextOverflow.Ellipsis
            )
            Text(
                text = if (expanded) "收起" else "阅读更多",
                style = MaterialTheme.typography.button,
                color = MaterialTheme.colors.primary
            )
        }
    }
}

注意animateContentSize() 的顺序很重要。通常将其放在 Modifier 链的较前位置,确保容器整体受到影响。


3. 列表项的灵动:Modifier.animateItem()

LazyColumn 中删除或重新排序项时,剩余项如果不动画化,会显得杂乱无章。

关键规则:

  1. 必须提供 Key :在 items(items, key = { it.id }) 中明确指定唯一 ID。
  2. API 更新 :在 Compose 1.7+ 中,建议使用 Modifier.animateItem() 替代旧的 animateItemPlacement()。它现在支持进入、退出、以及位置改变的全套动画。
Kotlin 复制代码
@Composable
fun TaskList(tasks: List<Task>) {
    LazyColumn {
        items(tasks, key = { it.id }) { task ->
            TaskRow(
                modifier = Modifier
                    .fillMaxWidth()
                    .animateItem( // 自动处理添加、删除和重新排序动画
                        fadeInSpec = tween(300),
                        placementSpec = spring(stiffness = Spring.StiffnessLow),
                        fadeOutSpec = fadeOut()
                    ),
                task = task
            )
        }
    }
}

💡 核心知识点对比 (FAQ)

场景 推荐工具 为什么不直接用属性动画?
组件显示/隐藏 AnimatedVisibility 它能管理生命周期,在动画结束后真正移除组件,释放布局空间。
容器尺寸变化 animateContentSize() 手动测量并用 animateDpAsState 改变高度非常复杂且性能较差。
列表重排 animateItem() 在 Lazy 布局中,它能自动处理 Item 的相对偏移和淡入淡出。

总结

通过掌握这三个工具,你的 UI 结构变化将不再生硬:

  • 使用 AnimatedVisibility 优雅地引入/移除组件。
  • 使用 animateContentSize() 让容器尺寸平滑呼吸。
  • 使用 animateItem() 赋予动态列表生命力。
相关推荐
玩机达人881 小时前
一加12/Ace5/Ace3pro手机ColorOS16降级回锁BL熔断变黑砖怎么办
android·智能手机·电脑
Digitally2 小时前
使用 USB 数据线将文件从安卓设备传输到电脑
android·电脑
xieliyu.3 小时前
MySQL 六大基础约束详解:NOT NULL/DEFAULT/UNIQUE/ 主键 / 外键 / CHECK
android·数据库·sql·mysql
Anokata3 小时前
MYSQL SQL 执行系列1
android·sql·mysql
REDcker4 小时前
Android 15 16KB 内存页适配详解
android·开发语言·python
Kapaseker4 小时前
Android 开放这扇门,正在被 Google 从里面反锁
android·kotlin
帅次4 小时前
Android 高级工程师面试:网络与数据层 近1年高频追问 18 题
android·网络·okhttp·面试·retrofit·gilde
三少爷的鞋5 小时前
Android 架构这些年,其实一直在消灭碎片化异步
android
plainGeekDev21 小时前
依赖管理 → Version Catalog
android·java·kotlin
韩曙亮1 天前
【Flutter】Flutter 进程保活 ③ ( 屏幕常亮设置 | Flutter 禁止息屏、关闭自动锁屏、屏蔽系统屏保 )
android·flutter·ios·屏幕常亮·禁止息屏