Jetpack Compose 动画实战:从基础 API 到性能优化全解析

为什么选择 Jetpack Compose 动画系统?
传统 Android 动画(Property Animator、ViewPropertyAnimator)需要大量样板代码,与 View 体系高度耦合。Jetpack Compose 的动画系统从设计之初就与声明式 UI 深度集成,让动画与状态变化自然绑定,代码量减少 60% 以上,同时获得更好的可组合性与可测试性。
声明式动画:动画跟随状态自动触发,无需手动启动/停止
协程原生支持:通过
suspend函数控制动画时序低 GC 压力:基于
State的更新路径,减少对象分配开箱即用的物理动画:弹簧(spring)、衰减(decay)效果内置支持
核心 API 速览:从简单到复杂
Compose 动画 API 分为高级 API 和低级 API 两层,理解各自的适用场景是掌握动画系统的关键。
高级 API
animateContentSize:Modifier 级别的尺寸变化动画,一行代码搞定
AnimatedVisibility:控制组件的显示/隐藏,支持自定义进出场动画
AnimatedContent:内容切换时的过渡动画,支持
ContentTransform自定义Crossfade:简单的淡入淡出内容切换
// AnimatedVisibility 示例
var visible by remember { mutableStateOf(true) }
AnimatedVisibility(
visible = visible,
enter = slideInVertically(initialOffsetY = { -it }) + fadeIn(),
exit = slideOutVertically(targetOffsetY = { -it }) + fadeOut()
) {
Card(modifier = Modifier.fillMaxWidth().padding(16.dp)) {
Text("我是动画卡片", modifier = Modifier.padding(16.dp))
}
}
Button(onClick = { visible = !visible }) {
Text(if (visible) "隐藏" else "显示")
}低级 API
animate*AsState:将普通值转换为动画值,如
animateFloatAsState、animateDpAsStateupdateTransition:管理多个动画值的状态机,适合多属性同步动画
rememberInfiniteTransition:无限循环动画,适合加载指示器、呼吸灯效果
Animatable:命令式动画控制,与协程配合实现复杂时序
// animate*AsState 示例:颜色与大小联动
var selected by remember { mutableStateOf(false) }
val bgColor by animateColorAsState(
targetValue = if (selected) MaterialTheme.colorScheme.primaryContainer
else MaterialTheme.colorScheme.surface,
animationSpec = tween(durationMillis = 300)
)
val scale by animateFloatAsState(
targetValue = if (selected) 1.05f else 1.0f,
animationSpec = spring(dampingRatio = Spring.DampingRatioMediumBouncy)
)
Box(
modifier = Modifier
.scale(scale)
.background(bgColor, RoundedCornerShape(12.dp))
.clickable { selected = !selected }
.padding(16.dp)
) {
Text("点我试试", style = MaterialTheme.typography.bodyLarge)
}实战场景一:列表项进场动画
列表是 Android 应用中最常见的 UI 形态,为列表项添加错落进场动画可以显著提升用户感知质量。以下是基于 LazyColumn 实现的交错动画方案:
@Composable
fun AnimatedListItem(
item: String,
index: Int,
modifier: Modifier = Modifier
) {
var visible by remember { mutableStateOf(false) }
LaunchedEffect(Unit) {
delay(index * 50L) // 错落延迟
visible = true
}
AnimatedVisibility(
visible = visible,
enter = fadeIn(tween(300)) + slideInHorizontally(
animationSpec = tween(300, easing = FastOutSlowInEasing),
initialOffsetX = { it / 2 }
),
modifier = modifier
) {
ListItem(
headlineContent = { Text(item) },
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp, vertical = 4.dp)
)
}
}
@Composable
fun AnimatedLazyList(items: List<String>) {
LazyColumn {
itemsIndexed(items) { index, item ->
AnimatedListItem(item = item, index = index)
}
}
}注意:LaunchedEffect(Unit) 确保动画只在组件首次进入组合时触发,避免重组时重复播放。
实战场景二:共享元素过渡(Compose 1.7+)
共享元素过渡(Shared Element Transition)是 Material 3 设计语言中的核心体验,Compose 1.7 正式稳定了这一 API。
// 在 SharedTransitionLayout 内使用
@Composable
fun SharedTransitionLayout(content: @Composable SharedTransitionScope.() -> Unit) {
// 官方 API,需要 androidx.compose.animation 1.7+
}
// 列表页
@Composable
fun SharedTransitionScope.ThumbnailItem(
item: PhotoItem,
animatedVisibilityScope: AnimatedVisibilityScope,
onClick: () -> Unit
) {
Image(
painter = rememberAsyncImagePainter(item.url),
contentDescription = null,
modifier = Modifier
.sharedElement(
state = rememberSharedContentState(key = "image-${item.id}"),
animatedVisibilityScope = animatedVisibilityScope
)
.size(80.dp)
.clip(RoundedCornerShape(8.dp))
.clickable(onClick = onClick)
)
}
// 详情页使用相同的 key 即可实现平滑过渡性能优化:避免过度重组
动画性能问题往往源于不必要的重组(Recomposition)。以下是经过实践验证的优化策略:
使用 derivedStateOf:当动画值被多个 Composable 读取时,用
derivedStateOf减少传播范围lambda 下沉:将读取动画状态的代码下沉到 Modifier lambda 中,只触发 Layout/Draw 阶段而非整个重组
避免在动画中读取非稳定对象:确保数据类实现
@Stable或使用@Immutable标注使用 graphicsLayer:平移、旋转、缩放等变换通过
graphicsLayer实现,绕过 Layout 阶段直接走 Draw
// ✅ 推荐:lambda 下沉,只触发 graphicsLayer 重绘
val scale by animateFloatAsState(targetValue = targetScale)
Box(
modifier = Modifier.graphicsLayer {
scaleX = scale // 在 lambda 内读取,不触发外部重组
scaleY = scale
}
)
// ❌ 避免:在外部读取,导致整个 Box 重组
Box(modifier = Modifier.scale(scale)) // 每帧都重组通过 Layout Inspector 的 Recomposition 高亮功能可以直观发现热点区域,配合 Compose 编译器指标报告(freeCompilerArgs += "-P", "plugin:androidx.compose.compiler.plugins.kotlin:reportsDestination=...")可以系统性地优化整个界面的重组效率。
发布评论
热门评论区: