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() 赋予动态列表生命力。
相关推荐
_李小白2 小时前
【android opencv学习笔记】Day 12: HSV 色彩空间
android·opencv·学习
千里马学框架2 小时前
手机大厂Activity嵌套模式及三分屏SplitScreen功能调研报告-独家干货
android·智能手机·分屏·aaos·安卓framework开发·车机·三分屏
Mr.QingBin2 小时前
SystemUI插件开发指南
android
芋只因2 小时前
MySQL 分库分表与 MyCat 的使用
android
Ehtan_Zheng2 小时前
Jetpack Compose 与 RecyclerView 混合布局的性能债
android
Kapaseker3 小时前
MVVM 旧城改造,边界划分各有招
android·kotlin
我滴老baby3 小时前
多智能体协作系统设计当AI学会团队合作效率翻十倍
android·开发语言·人工智能
StockTV4 小时前
新加坡股票API 实时行情、K 线及指数数据
android·java·spring boot·后端·区块链
草莓熊Lotso4 小时前
LangChain从入门到精通:环境搭建→核心能力→LCEL链式编程全实战
android·java·linux·服务器·langchain