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() 赋予动态列表生命力。
相关推荐
五彩小白4 小时前
自回归和上下文
android
事圆则缓6 小时前
Android 多渠道打包实战:Product Flavors、签名、AAB 与发包校验
android
树下困觉眯8 小时前
View绘制流程学习--获取WindowManager的三种方式
android·系统架构·view design
vilya9 小时前
我怎么给手机 GUI Agent 做双通道感知:无障碍树为主,投屏像素兜底
android·人工智能
李子红了时10 小时前
阿狸舞台APP(安卓端手机EncFS解密+压缩包解压+视频播放)
android·音视频
事圆则缓10 小时前
Android 使用 Jenkins 实现 CI/CD,并用 SonarQube 建立质量门禁
android·ci/cd·jenkins
其实防守也摸鱼10 小时前
DeepSeek Harness 开源贡献手记:从 Issue 到 Merge 的完整旅程
android·数据库·学习·ai·oracle·自动化
恋猫de小郭10 小时前
Android CLI 支持 AI Agent 通过 Device Streaming 调试云真机
android·前端·flutter
素师良码1 天前
第5篇:显示驱动必备调试工具浅析
android
维克兜率天1 天前
【维克】配对交易的季节性:哪些品种适合长拿?
android·开发语言·笔记·python·算法·kotlin·量化