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() 赋予动态列表生命力。
相关推荐
一笑的小酒馆5 小时前
Android12Launcher3实现应用裁剪
android
FinelyYang11 小时前
UniApp + LiveKit:Android 端为什么要用原生做屏幕共享,以及我们怎么落地的
android·uni-app
蓝速科技11 小时前
蓝速科技信创落地:平衡安全合规与项目成本的实战方案
android·运维·人工智能·科技·安全·电脑·鸿蒙
又见情义13 小时前
RK3568 Android 13 SDK 开机自动开启共享热点实践
android
mmsx14 小时前
return false 不是拦截:一次按键触发两个动作的事件冒泡陷阱
android·bug
DU_YULIN15 小时前
Camera HAL ThreadManager概述
android·camera hal
淡淡的香烟15 小时前
Android中的MQTT通信原理解析
android·物联网
淡淡的香烟15 小时前
Android Camera开发详解
android·音视频
mmsx16 小时前
基于 Android 的校园信息管理系统源码
android·java·okhttp
YF021117 小时前
如何进入Android工程模式
android