Compose 动画
问题
Compose 中有哪些动画 API?如何选择合适的动画方案?
答案
1. 动画 API 选择
2. animateXxxAsState(最常用)
状态驱动的单值动画:
@Composable
fun ExpandableCard(isExpanded: Boolean) {
// 高度动画
val height by animateDpAsState(
targetValue = if (isExpanded) 200.dp else 80.dp,
animationSpec = spring(dampingRatio = Spring.DampingRatioMediumBouncy),
label = "height"
)
// 颜色动画
val backgroundColor by animateColorAsState(
targetValue = if (isExpanded) Color.Blue else Color.Gray,
label = "color"
)
Box(
modifier = Modifier
.fillMaxWidth()
.height(height)
.background(backgroundColor, RoundedCornerShape(12.dp))
)
}
3. AnimatedVisibility
控制内容的显示/隐藏动画:
@Composable
fun FloatingMenu(isVisible: Boolean) {
AnimatedVisibility(
visible = isVisible,
enter = fadeIn() + slideInVertically(),
exit = fadeOut() + slideOutVertically()
) {
Column {
MenuItem("编辑")
MenuItem("删除")
MenuItem("分享")
}
}
}
4. AnimatedContent
内容切换动画:
@Composable
fun CounterDisplay(count: Int) {
AnimatedContent(
targetState = count,
transitionSpec = {
if (targetState > initialState) {
// 数字增大:新内容从上方滑入,旧内容从下方滑出
slideInVertically { -it } + fadeIn() togetherWith
slideOutVertically { it } + fadeOut()
} else {
slideInVertically { it } + fadeIn() togetherWith
slideOutVertically { -it } + fadeOut()
}.using(SizeTransform(clip = false))
},
label = "counter"
) { targetCount ->
Text(
text = "$targetCount",
style = MaterialTheme.typography.displayLarge
)
}
}
5. AnimationSpec 类型
| Spec | 说明 | 适用场景 |
|---|---|---|
spring() | 弹簧物理动画(默认) | 大部分场景 |
tween() | 固定时长 + 缓动曲线 | 精确控制时长 |
keyframes() | 关键帧动画 | 复杂中间状态 |
snap() | 立即跳转,无过渡 | 开关切换 |
infiniteRepeatable() | 无限重复 | 加载动画 |
// 弹簧动画
spring<Float>(dampingRatio = 0.5f, stiffness = 300f)
// 缓动动画
tween<Float>(durationMillis = 300, easing = FastOutSlowInEasing)
// 无限重复
val alpha by rememberInfiniteTransition(label = "pulse").animateFloat(
initialValue = 0.3f,
targetValue = 1f,
animationSpec = infiniteRepeatable(
animation = tween(500),
repeatMode = RepeatMode.Reverse
),
label = "alpha"
)
常见面试问题
Q1: animateXxxAsState 和 Animatable 的区别?
答案:
animateXxxAsState:声明式 API,给定目标值后自动动画到目标。不支持手动控制(停止、跳转)。适合简单的状态驱动动画Animatable:命令式 API,可以在协程中精确控制:snapTo()、animateTo()、stop()。适合手势驱动或需要精确控制的动画
val offset = remember { Animatable(0f) }
LaunchedEffect(targetOffset) {
offset.animateTo(targetOffset, animationSpec = spring())
}
Q2: updateTransition 有什么用?
答案:
updateTransition 在一个状态机中管理多个属性的协调动画:
val transition = updateTransition(targetState = tabState, label = "tab")
val indicatorLeft by transition.animateDp(label = "left") { state -> state.left }
val indicatorWidth by transition.animateDp(label = "width") { state -> state.width }
val color by transition.animateColor(label = "color") { state -> state.color }
所有属性会同步开始和结束,确保动画一致性。
Q3: Compose 动画如何调试和预览?
答案:
通过 Android Studio 的 Animation Preview 工具:
- 在
@Preview中使用updateTransition,Studio 可以逐帧预览 - 使用
label参数命名动画,方便在 Animation Inspector 中识别 Modifier.animateContentSize()的finishedListener可以监听动画结束