前言 简单动画推荐使用 animate*AsState 或 rememberInfiniteTransition,复杂交互动画则适合 Animatable 或 updateTransition。
个人比较喜欢使用Animatable,灵活方便。
单属性动画 Animatable 循环播放 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 import androidx.compose.animation.core.Animatableimport androidx.compose.animation.core.RepeatModeimport androidx.compose.animation.core.infiniteRepeatableimport androidx.compose.animation.core.tweenimport androidx.compose.foundation.Canvasimport androidx.compose.foundation.layout.paddingimport androidx.compose.foundation.layout.sizeimport androidx.compose.runtime.Composableimport androidx.compose.runtime.LaunchedEffectimport androidx.compose.runtime.rememberimport androidx.compose.ui.Modifierimport androidx.compose.ui.geometry.Sizeimport androidx.compose.ui.graphics.Colorimport androidx.compose.ui.tooling.preview.Previewimport androidx.compose.ui.unit.dp@Composable fun AnimDemo () { val animProgress = remember { Animatable(50f ) } LaunchedEffect(Unit ) { animProgress.animateTo( targetValue = 200f , animationSpec = infiniteRepeatable( animation = tween(1200 ), repeatMode = RepeatMode.Reverse ) ) } Canvas( modifier = Modifier .padding(40. dp) .size(200. dp) ) { drawRect( color = Color.Red, size = Size(animProgress.value.dp.toPx(), animProgress.value.dp.toPx()) ) } } @Preview @Composable fun AnimDemoPreview () { AnimDemo() }
单次播放 1 2 3 4 5 6 7 val animProgress = remember { Animatable(0.5f ) }LaunchedEffect(Unit ) { animProgress.animateTo( targetValue = 1f , animationSpec = tween(1200 ) ) }
重置 1 2 3 4 5 6 7 8 val animProgress = remember { Animatable(0.6f ) }LaunchedEffect(vm.selectMenuIndex.value) { animProgress.snapTo(0.6f ) animProgress.animateTo( targetValue = 1f , animationSpec = tween(600 ) ) }
其中
animProgress.snapTo(0.6f)可以直接修改值而不执行动画。
animate*AsState 使用 animate*AsState 系列 API
这是最基础的动画方式,通过监听状态变化自动生成动画值,适用于简单的属性动画。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 import androidx.compose.animation.core.animateFloatAsStateimport androidx.compose.animation.core.tweenimport androidx.compose.foundation.Canvasimport androidx.compose.foundation.layout.paddingimport androidx.compose.foundation.layout.sizeimport androidx.compose.runtime.Composableimport androidx.compose.runtime.LaunchedEffectimport androidx.compose.runtime.getValueimport androidx.compose.runtime.mutableStateOfimport androidx.compose.runtime.rememberimport androidx.compose.runtime.setValueimport androidx.compose.ui.Modifierimport androidx.compose.ui.graphics.Colorimport androidx.compose.ui.graphics.drawscope.Strokeimport androidx.compose.ui.tooling.preview.Previewimport androidx.compose.ui.unit.dp@Composable fun AnimDemo () { var isStart by remember { mutableStateOf(false ) } val progress by animateFloatAsState( targetValue = if (isStart) 1f else 0f , animationSpec = tween(durationMillis = 2000 ), finishedListener = { println("动画已完成" ) } ) LaunchedEffect(Unit ) { isStart = true } Canvas( modifier = Modifier .padding(40. dp) .size(200. dp) ) { drawArc( color = Color.Blue, startAngle = 0f , sweepAngle = 360f * progress, useCenter = false , style = Stroke(width = 8. dp.toPx()) ) } } @Preview @Composable fun AnimDemoPreview () { AnimDemo() }
rememberInfiniteTransition 使用 rememberInfiniteTransition 实现无限动画 适用于需要无限循环的动画,如加载指示器。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 import androidx.compose.animation.core.LinearEasingimport androidx.compose.animation.core.animateFloatimport androidx.compose.animation.core.infiniteRepeatableimport androidx.compose.animation.core.rememberInfiniteTransitionimport androidx.compose.animation.core.tweenimport androidx.compose.foundation.Canvasimport androidx.compose.foundation.layout.paddingimport androidx.compose.foundation.layout.sizeimport androidx.compose.runtime.Composableimport androidx.compose.runtime.getValueimport androidx.compose.ui.Modifierimport androidx.compose.ui.geometry.Offsetimport androidx.compose.ui.geometry.Sizeimport androidx.compose.ui.graphics.Colorimport androidx.compose.ui.graphics.drawscope.rotateimport androidx.compose.ui.tooling.preview.Previewimport androidx.compose.ui.unit.dp@Composable fun AnimDemo () { val infiniteTransition = rememberInfiniteTransition() val rotation by infiniteTransition.animateFloat( initialValue = 0f , targetValue = 360f , animationSpec = infiniteRepeatable( animation = tween(2000 , easing = LinearEasing) ) ) Canvas(modifier = Modifier .padding(40. dp) .size(200. dp)) { rotate(rotation) { drawRect( color = Color.Red, topLeft = Offset(75. dp.toPx(), 75. dp.toPx()), size = Size(50. dp.toPx(), 50. dp.toPx()) ) } } } @Preview @Composable fun AnimDemoPreview () { AnimDemo() }
updateTransition 使用 updateTransition 管理多状态过渡
适合在多个状态之间平滑过渡,如组件的展开 / 折叠 / 隐藏状态切换。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 import androidx.compose.animation.core.animateDpimport androidx.compose.animation.core.tweenimport androidx.compose.animation.core.updateTransitionimport androidx.compose.foundation.Canvasimport androidx.compose.foundation.layout.paddingimport androidx.compose.foundation.layout.sizeimport androidx.compose.runtime.Composableimport androidx.compose.runtime.LaunchedEffectimport androidx.compose.runtime.getValueimport androidx.compose.runtime.mutableStateOfimport androidx.compose.runtime.rememberimport androidx.compose.runtime.setValueimport androidx.compose.ui.Modifierimport androidx.compose.ui.geometry.Sizeimport androidx.compose.ui.graphics.Colorimport androidx.compose.ui.tooling.preview.Previewimport androidx.compose.ui.unit.dpsealed class State { object Collapsed : State() object Expanded : State() } @Composable fun AnimDemo () { var state: State by remember { mutableStateOf(State.Collapsed) } val transition = updateTransition(state, label = "state transition" ) val size by transition.animateDp( transitionSpec = { tween(1000 ) }, label = "size" ) { when (it) { is State.Collapsed -> 50. dp is State.Expanded -> 200. dp } } LaunchedEffect(Unit ) { state = State.Expanded } Canvas( modifier = Modifier .padding(40. dp) .size(200. dp) ) { drawRect( color = Color.Red, size = Size(size.toPx(), size.toPx()) ) } } @Preview @Composable fun AnimDemoPreview () { AnimDemo() }
组合动画 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 import androidx.compose.animation.core.Animatableimport androidx.compose.animation.core.tweenimport androidx.compose.foundation.Canvasimport androidx.compose.foundation.layout.sizeimport androidx.compose.runtime.Composableimport androidx.compose.runtime.LaunchedEffectimport androidx.compose.runtime.rememberimport androidx.compose.ui.Modifierimport androidx.compose.ui.geometry.Offsetimport androidx.compose.ui.geometry.Sizeimport androidx.compose.ui.graphics.Colorimport androidx.compose.ui.graphics.drawscope.withTransformimport androidx.compose.ui.tooling.preview.Previewimport androidx.compose.ui.unit.dpimport kotlinx.coroutines.launch@Composable fun AnimDemo () { val scale = remember { Animatable(1f ) } val rotation = remember { Animatable(0f ) } LaunchedEffect(Unit ) { launch { scale.animateTo( targetValue = 2f , animationSpec = tween(1000 ) ) } launch { rotation.animateTo( targetValue = 360f , animationSpec = tween(1000 ) ) } } Canvas(modifier = Modifier.size(200. dp)) { withTransform( transformBlock = { scale(scale.value, scale.value) rotate(rotation.value) } ) { drawRect( color = Color.Green, size = Size(50. dp.toPx(), 50. dp.toPx()), topLeft = Offset(center.x - 25. dp.toPx(), center.y - 25. dp.toPx()) ) } } } @Preview @Composable fun AnimDemoPreview () { AnimDemo() }
动画类型 缓动动画 tween
缓动曲线(Easing) ,用于控制动画的速度变化节奏。
它们基于贝塞尔曲线(Cubic Bezier)实现,能让动画效果更符合物理规律或视觉预期,避免机械感的匀速运动。
1 tween(durationMillis = 1200 , easing = LinearEasing)
easing值
默认是FastOutSlowInEasing
1 2 3 4 5 6 7 8 9 10 11 public val FastOutSlowInEasing: Easing = CubicBezierEasing(0.4f , 0.0f , 0.2f , 1.0f )public val LinearOutSlowInEasing: Easing = CubicBezierEasing(0.0f , 0.0f , 0.2f , 1.0f )public val FastOutLinearInEasing: Easing = CubicBezierEasing(0.4f , 0.0f , 1.0f , 1.0f )public val LinearEasing: Easing = Easing { fraction -> fraction }
单次动画时长2秒,线性匀速
1 animation = tween(2000 , easing = LinearEasing)
弹簧动画 弹簧动画(不能用在infiniteRepeatable中)
1 2 3 4 5 6 7 8 9 val animProgress = remember { Animatable(50f ) }LaunchedEffect(Unit ) { animProgress.animateTo( targetValue = 200f , animationSpec = spring( visibilityThreshold = 0.1f , ) ) }
关键帧动画 1 2 3 4 5 6 7 animation = keyframes { durationMillis = 2000 0f at 0 180f at 100 100f at 1000 200f at 2000 }
跳变 1.2秒后直接跳变
循环播放 1 2 3 4 5 6 7 animProgress.animateTo( targetValue = 200f , animationSpec = infiniteRepeatable( animation = tween(1200 ), repeatMode = RepeatMode.Reverse ) )
在 Jetpack Compose 中,infiniteRepeatable() 是用于创建无限循环动画的关键函数,通常配合 rememberInfiniteTransition 使用。它的参数用于控制循环方式、动画曲线、重复次数等。
以下是详细说明:
函数签名 1 2 3 4 5 fun <T> infiniteRepeatable ( animation: AnimationSpec <T >, repeatMode: RepeatMode = RepeatMode.Restart, initialStartOffset: StartOffset = StartOffset(0 ) ): InfiniteRepeatableSpec<T>
核心参数说明 动画类型 animation: AnimationSpec<T>(必填)
作用 :定义单次动画的行为(如时长、缓动曲线等)
常用值 :
tween():指定固定时长的动画(最常用)
spring():弹簧物理效果的动画
keyframes():关键帧动画
snap():无动画,直接跳变
循环方式 repeatMode: RepeatMode(可选,默认 Restart)
作用 :定义每次循环的方式
取值 :
RepeatMode.Restart:每次循环从初始值重新开始
RepeatMode.Reverse:交替反向播放(如从0→1→0→1…)
示例:先正向播放,再反向播放,循环往复
1 repeatMode = RepeatMode.Reverse
延迟 initialStartOffset: StartOffset(可选,默认 StartOffset(0))
作用 :定义动画首次启动的延迟或偏移
使用场景 :
延迟首次动画开始时间
让多个循环动画错开启动(实现错开的波浪效果)
1 2 initialStartOffset = StartOffset(500 )
注意:offsetMillis 是相对于动画总时长的偏移(如2000ms动画,偏移1000ms即从中间开始)
完整示例 以下是一个结合 infiniteRepeatable 参数的旋转+缩放动画:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 @Composable fun InfiniteAnimationDemo () { val infiniteTransition = rememberInfiniteTransition() val rotation by infiniteTransition.animateFloat( initialValue = 0f , targetValue = 360f , animationSpec = infiniteRepeatable( animation = tween(3000 , easing = LinearEasing), repeatMode = RepeatMode.Restart ) ) val scale by infiniteTransition.animateFloat( initialValue = 1f , targetValue = 1.5f , animationSpec = infiniteRepeatable( animation = tween(1000 ), repeatMode = RepeatMode.Reverse, initialStartOffset = StartOffset(500 ) ) ) Canvas(modifier = Modifier.size(200. dp)) { withTransform({ rotate(rotation) scale(scale, scale) }) { drawCircle( color = Color.Magenta, radius = 50. dp.toPx(), center = center ) } } }
关键注意点
infiniteRepeatable 必须配合 rememberInfiniteTransition 使用,不能直接用于 animate*AsState
repeatMode = Reverse 时,动画会在 initialValue 和 targetValue 之间来回切换
多个动画可以共享同一个 infiniteTransition,通过 initialStartOffset 实现错开效果
若需要停止无限动画,可通过控制 rememberInfiniteTransition 的生命周期(如用 LaunchedEffect 的 key 参数)
通过组合这些参数,可以实现各种复杂的循环动画效果,如加载指示器、呼吸效果、旋转动画等。
常见示例 旋转动画 无限匀速旋转动画
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 val rotation = remember { Animatable(0f ) }LaunchedEffect(Unit ) { rotation.animateTo( targetValue = 360f , animationSpec = infiniteRepeatable( animation = tween(8000 , easing = LinearEasing), repeatMode = RepeatMode.Restart ) ) } Box( Modifier .size(302. dp) .rotate(rotation.value) ) { ZImgLocalBg(R.drawable.ques_loading_bg_1) }