You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.16 00:53:22