:Compose 中的动画 —— 从简单过渡到复杂交互引言:动画让应用活起来在之前的教程中,我们零散地使用过动画:点击按钮的缩放效果、列表项进入的淡入淡出)
Android 开发入门教程第四十九篇Compose 中的动画 —— 从简单过渡到复杂交互引言动画让应用活起来在之前的教程中我们零散地使用过动画点击按钮的缩放效果、列表项进入的淡入淡出、页面切换的滑动等。但动画远不止这些。动画不仅仅是“让界面变得好看”。好的动画能够引导用户注意力新消息闪烁提醒用户查看传达状态变化加载指示器告诉用户正在处理提供反馈按钮按下有视觉反馈让用户感觉“点到了”增加趣味性精美的动画让用户愿意使用你的应用Compose 的动画系统是声明式的。你不需要创建Animator、AnimatorSet也不需要实现AnimatorUpdateListener。在 Compose 中动画是状态驱动的——动画的目标值绑定到状态状态变化时动画自动执行UI 自动更新。本篇将系统讲解 Compose 中的动画简单属性动画animate*AsState可见性动画AnimatedVisibility内容大小动画animateContentSize多属性并行动画Transition无限重复动画rememberInfiniteTransition手势驱动的动画Animatable自定义动画与关键帧性能优化与最佳实践完整实战带动画的购物车注意本篇需要你对 Compose 状态管理第 5 篇和手势处理第 16 篇有基本了解。一、简单属性动画animate*AsState1.1 基本用法animate*AsState系列函数是最常用的动画 API。它们将目标值绑定到状态当状态变化时值平滑过渡。kotlinComposable fun SimpleAnimation() { var isBig by remember { mutableStateOf(false) } val size by animateDpAsState( targetValue if (isBig) 100.dp else 50.dp, animationSpec spring() ) Box( modifier Modifier .size(size) .background(Color.Blue) .clickable { isBig !isBig } ) }1.2 动画规格AnimationSpec动画规格控制动画的时长、曲线、重复方式。Spring弹性动画— 自然、有弹性的感觉kotlinspring( dampingRatio Spring.DampingRatioMediumBouncy, // 弹性强度 stiffness Spring.StiffnessMedium, // 刚度 visibilityThreshold 0.01f // 停止阈值 )Tween补间动画— 精确控制时长和曲线kotlintween( durationMillis 300, delayMillis 0, easing FastOutSlowInEasing )关键帧动画— 多阶段控制kotlinkeyframes { durationMillis 1000 0f at 0 with LinearEasing 0.5f at 300 with FastOutSlowInEasing 1f at 800 1.2f at 1000 // 过冲效果 }重复动画kotlinrepeatable( iterations 3, animation tween(300), repeatMode RepeatMode.Restart // 或 Reverse ) infiniteRepeatable( animation tween(300), repeatMode RepeatMode.Reverse )1.3 多种类型的 animate*AsState函数返回类型适用场景animateFloatAsStateFloat透明度、旋转角度、进度animateDpAsStateDp尺寸、边距、偏移animateColorAsStateColor颜色过渡animateIntAsStateInt整数动画animateOffsetAsStateOffset位置移动animateSizeAsStateSize尺寸变化1.4 组合示例带动画的按钮kotlinComposable fun AnimatedButton() { var isPressed by remember { mutableStateOf(false) } val scale by animateFloatAsState( targetValue if (isPressed) 0.95f else 1f, animationSpec spring(dampingRatio Spring.DampingRatioMediumBouncy) ) val elevation by animateDpAsState( targetValue if (isPressed) 0.dp else 4.dp, animationSpec tween(100) ) Button( onClick { /* 执行操作 */ }, modifier Modifier .scale(scale) .shadow(elevation), interactionSource remember { MutableInteractionSource() } .also { interactionSource - LaunchedEffect(interactionSource) { interactionSource.interactions.collect { interaction - isPressed interaction is PressInteraction.Press } } } ) { Text(点击我) } }二、可见性动画AnimatedVisibility2.1 基本用法AnimatedVisibility控制组件的出现和消失。kotlinComposable fun VisibilityAnimation() { var visible by remember { mutableStateOf(true) } Column { Button(onClick { visible !visible }) { Text(if (visible) 隐藏 else 显示) } AnimatedVisibility( visible visible, enter fadeIn() slideInVertically(), exit fadeOut() slideOutVertically() ) { Box( modifier Modifier .size(200.dp) .background(Color.Blue) ) } } }2.2 入场/出场效果入场效果出场效果说明fadeIn()fadeOut()淡入/淡出slideInHorizontally()slideOutHorizontally()水平滑入/滑出slideInVertically()slideOutVertically()垂直滑入/滑出expandIn()shrinkOut()从中心放大/缩小scaleIn()scaleOut()缩放2.3 自定义效果kotlin// 从左向右滑入 val customEnter slideInHorizontally( initialOffsetX { -it }, animationSpec tween(300, easing FastOutSlowInEasing) ) fadeIn(animationSpec tween(300)) // 向右滑出 val customExit slideOutHorizontally( targetOffsetX { it }, animationSpec tween(300) ) fadeOut(animationSpec tween(300)) AnimatedVisibility( visible visible, enter customEnter, exit customExit ) { // 内容 }三、内容大小动画animateContentSize3.1 基本用法animateContentSize让内容大小变化时平滑过渡。kotlinComposable fun ExpandableCard() { var expanded by remember { mutableStateOf(false) } Card( modifier Modifier .fillMaxWidth() .padding(8.dp) .clickable { expanded !expanded } ) { Column( modifier Modifier .animateContentSize( animationSpec spring( dampingRatio Spring.DampingRatioMediumBouncy ) ) .padding(16.dp) ) { Text(标题, fontWeight FontWeight.Bold) if (expanded) { Spacer(modifier Modifier.height(8.dp)) Text(这是展开后显示的详细内容。当点击卡片时高度会平滑地变化。) Spacer(modifier Modifier.height(8.dp)) Text(可以包含任意多的内容。) } } } }3.2 在 LazyColumn 中使用kotlinComposable fun ExpandableList() { val items (1..20).map { 条目 $it } var expandedIndex by remember { mutableStateOfInt?(null) } LazyColumn { itemsIndexed(items) { index, item - Card( modifier Modifier .fillMaxWidth() .padding(8.dp) .clickable { expandedIndex if (expandedIndex index) null else index } ) { Column( modifier Modifier .animateContentSize() .padding(16.dp) ) { Text(item, fontWeight FontWeight.Bold) if (expandedIndex index) { Spacer(modifier Modifier.height(8.dp)) Text(这是 $item 的详细内容。) Text(可以包含更多信息。) } } } } } }四、多属性并行动画Transition4.1 基本用法updateTransition让多个属性同时动画共享同一个状态。kotlinComposable fun MultiPropertyAnimation() { var isSelected by remember { mutableStateOf(false) } val transition updateTransition(targetState isSelected, label select) val backgroundColor by transition.animateColor(label bg) { selected - if (selected) Color.Green else Color.Red } val size by transition.animateDp(label size) { selected - if (selected) 150.dp else 100.dp } val rotation by transition.animateFloat(label rotation) { selected - if (selected) 360f else 0f } Box( modifier Modifier .size(size) .rotate(rotation) .background(backgroundColor) .clickable { isSelected !isSelected } ) }4.2 使用 Transition 管理复杂状态