传统的 Android View 动画体系(AlphaAnimation, ObjectAnimator, MotionLayout 等)往往割裂且复杂。而 Jetpack Compose 的动画 API 设计得非常直观 且声明式------你只需要描述"状态 A"和"状态 B",Compose 会自动帮你处理中间的过渡。
本文将由浅入深,带你掌握 Compose 动画的三板斧。
1. 入门:属性动画 (animate*AsState)
这是最简单、最常用的动画方式。当你只需要改变某个单一属性(如颜色、大小、透明度)时,使用它准没错。
场景:点击按钮,方块变大,颜色变红。
Kotlin
@Composable
fun SimpleAnimationDemo() {
// 1. 定义状态
var isBig by remember { mutableStateOf(false) }
// 2. 定义动画值 (Compose 会自动处理从当前值到目标值的平滑过渡)
val size by animateDpAsState(
targetValue = if (isBig) 100.dp else 50.dp,
label = "size" // 用于 Layout Inspector 调试
)
val color by animateColorAsState(
targetValue = if (isBig) Color.Red else Color.Blue,
label = "color"
)
// 3. 使用动画值
Box(
modifier = Modifier
.size(size)
.background(color)
.clickable { isBig = !isBig }
)
}
常用的 API 有:
animateDpAsState:用于dp单位的值(如宽高、padding、offset)。animateFloatAsState:用于浮点数值(如透明度alpha、缩放scale、旋转角度)。animateColorAsState:用于颜色值(如背景色、文字颜色),会自动处理 RGB 的平滑过渡。animateIntAsState:用于整数值(如列表索引、百分比进度)。
2. 进阶:可见性动画 (AnimatedVisibility)
在 View 体系中,隐藏/显示通常只是 View.GONE / View.VISIBLE 的生硬切换。Compose 提供了 AnimatedVisibility,让组件的出现和消失变得丝般顺滑。
场景:点击按钮,展开/收起一段文本。
Kotlin
@Composable
fun VisibilityDemo() {
var visible by remember { mutableStateOf(true) }
Column {
Button(onClick = { visible = !visible }) {
Text(if (visible) "Hide" else "Show")
}
AnimatedVisibility(
visible = visible,
// 自定义进入动画:从顶部滑入 + 淡入
enter = slideInVertically() + fadeIn(),
// 自定义退出动画:向顶部滑出 + 淡出
exit = slideOutVertically() + fadeOut()
) {
// 这里放你想隐藏/显示的内容
Text(
text = "Peek-a-boo! I see you!",
modifier = Modifier.padding(16.dp).background(Color.Yellow)
)
}
}
}
Tips :你可以通过 + 号组合多个动画效果(如"淡入"+"放大")。
3. 高级:布局内容切换 (AnimatedContent)
当你需要根据状态完全替换 界面上的某个部分(例如:从"加载中"切换到"数据内容",或者数字的滚动变化)时,AnimatedContent 是神器。
场景:一个简单的计数器,数字变化时有类似老式翻页钟的上下滚动效果。
Kotlin
@Composable
fun CounterDemo() {
var count by remember { mutableIntStateOf(0) }
Column(horizontalAlignment = Alignment.CenterHorizontally) {
Button(onClick = { count++ }) { Text("Add") }
AnimatedContent(
targetState = count,
transitionSpec = {
// 定义:新数字从下往上进,旧数字从上往出
// (slideInVertically { height -> height } + fadeIn())
// .togetherWith(slideOutVertically { height -> -height } + fadeOut())
// 简化写法:垂直滚动动画
slideInVertically { it } togetherWith slideOutVertically { -it }
},
label = "counter"
) { targetCount ->
// 这里不仅可以是 Text,也可以是复杂的 Composable
Text(
text = "$targetCount",
style = MaterialTheme.typography.displayLarge
)
}
}
}
4. 核心配置:动画规格 (AnimationSpec)
几乎所有的 Compose 动画 API 都有一个 animationSpec 参数,用于控制动画的速度 、曲线 和质感。
A. 弹簧效果 (Spring) - 默认且推荐
如果你不设置,Compose 默认使用 spring()。它模拟物理世界的弹簧,没有固定的时长,只有阻尼和刚度,看起来最自然。
Kotlin
animateDpAsState(
targetValue = 100.dp,
animationSpec = spring(
dampingRatio = Spring.DampingRatioMediumBouncy, // 弹性系数 (高弹/低弹/无弹)
stiffness = Spring.StiffnessLow // 刚度 (越低越慢且软)
)
)
B. 补间动画 (Tween)
传统的基于时长的动画。
Kotlin
animateDpAsState(
targetValue = 100.dp,
animationSpec = tween(
durationMillis = 1000, // 1秒
easing = LinearOutSlowInEasing // 缓动曲线
)
)
C. 关键帧 (Keyframes)
用于精细控制动画过程中的每一个节点。
Kotlin
animateDpAsState(
targetValue = 100.dp,
animationSpec = keyframes {
durationMillis = 1000
0.dp at 0 // 开始时 0dp
50.dp at 200 // 200ms 时到达 50dp (快)
100.dp at 1000 // 结束时 100dp
}
)
5. 总结与建议
Compose 的动画系统非常庞大,但对于 90% 的日常开发,只需要记住以下决策路径:
- 只是改变某个属性值? -> 用
animate*AsState。 - 让组件出现/消失? -> 用
AnimatedVisibility。 - 两个组件互相切换? -> 用
AnimatedContent。