在Jetpack Compose中用AnimatedContent实现动画链式执行的方案问询
解决RollingDigit动画链式执行的健壮方案
你当前的实现通过固定delay等待动画结束,这种方式在不同性能设备上可能出现动画不同步的问题。下面提供两种可靠的方案,确保前一个滚动动画完全结束后再启动下一个,同时保留原有的减速效果:
方案1:监听AnimatedContent的动画运行状态
利用AnimatedContent的Transition对象监听动画是否正在运行,只有当前动画完全结束后,才触发下一次数字更新和滚动动画。
修改后的完整代码:
@OptIn(ExperimentalAnimationApi::class) @Composable fun RollingDigit( targetDigit: Int, textStyle: TextStyle, delay: Long = (Random.nextFloat() * 300).roundToLong(), ) { var targetOffset by remember { mutableStateOf(Random.nextInt(15, 30)) } var currentDigit by remember { mutableStateOf(getSourceDigit(targetDigit, targetOffset)) } var animationDuration by remember { mutableStateOf(100L) } // 保存当前动画的Transition实例,用于监听状态 var currentTransition by remember { mutableStateOf<AnimatedContentTransitionScope<Int>?>(null) } LaunchedEffect(targetDigit) { targetOffset = Random.nextInt(15, 30) animationDuration = 100L currentDigit = getSourceDigit(targetDigit, targetOffset) delay(delay) while (targetOffset > 0) { // 循环等待,直到当前动画停止运行 while (currentTransition?.isRunning == true) { delay(16) // 按帧间隔等待,避免空轮询 } // 更新参数,触发下一次滚动动画 targetOffset-- animationDuration += ((10 - targetOffset) * 10L).coerceAtLeast(0L) currentDigit = (currentDigit + 1) % 10 } } AnimatedContent( targetState = currentDigit, transitionSpec = { slideInVertically( animationSpec = tween(animationDuration.toInt()), initialOffsetY = { -it }, ) with slideOutVertically( animationSpec = tween(animationDuration.toInt()), targetOffsetY = { it } ) } ) { current -> // 将当前Transition实例保存到状态中 currentTransition = this Text( text = "$current", style = textStyle, ) } } private fun getSourceDigit(targetDigit: Int, targetOffset: Int): Int { val difference = (targetDigit - targetOffset) % 10 return if (difference < 0) difference + 10 else difference } @Preview @Composable fun StackOverflowExample() { RollingDigit( targetDigit = 1, textStyle = MaterialTheme.typography.headlineMedium, ) }
方案2:使用帧精确的挂起等待
通过withFrameNanosAPI精确等待动画时长,避免依赖固定delay,确保动画完全执行完毕后再进入下一轮滚动。
修改后的完整代码:
@OptIn(ExperimentalAnimationApi::class) @Composable fun RollingDigit( targetDigit: Int, textStyle: TextStyle, delay: Long = (Random.nextFloat() * 300).roundToLong(), ) { val initialOffset = remember { Random.nextInt(15, 30) } var currentDigit by remember { mutableStateOf(getSourceDigit(targetDigit, initialOffset)) } var remainingOffsets by remember { mutableStateOf(initialOffset) } var currentDuration by remember { mutableStateOf(100L) } LaunchedEffect(targetDigit) { delay(delay) while (remainingOffsets > 0) { // 更新当前动画时长,保持减速逻辑 currentDuration = (100 + ((10 - remainingOffsets) * 10).coerceAtLeast(0)).toLong() // 触发数字更新,启动新的滚动动画 currentDigit = (currentDigit + 1) % 10 // 精确等待当前动画时长结束 val startTime = withFrameNanos { it } while (withFrameNanos { it - startTime } < currentDuration * 1_000_000) { // 空循环等待,直到动画时长耗尽 } remainingOffsets-- } } AnimatedContent( targetState = currentDigit, transitionSpec = { slideInVertically( animationSpec = tween(currentDuration.toInt()), initialOffsetY = { -it }, ) with slideOutVertically( animationSpec = tween(currentDuration.toInt()), targetOffsetY = { it } ) } ) { Text( text = "$it", style = textStyle, ) } }
核心改进说明
- 移除不可靠的固定delay:两种方案都通过监听动画状态或精确计时,确保动画完成后再执行下一轮更新,适配不同性能的设备。
- 保留原减速逻辑:完整保留了原代码中
animationDuration递增的逻辑,实现滚动后期减速的需求。 - 状态同步更稳定:避免了因设备性能差异导致的动画重叠、提前结束或卡顿问题。
内容的提问来源于stack exchange,提问作者220284
相关产品推荐
相关产品推荐

