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

如何让Jetpack Compose中的CircularProgressIndicator动画更流畅?

解决CircularProgressIndicator计时器平滑过渡的问题

你的问题核心在于每秒才更新一次进度值,导致进度条只能跳变。要实现流畅过渡,需要让进度值持续、精细地变化,而不是每秒跳一次。下面是具体的修改方案:

核心思路

放弃维护整数秒的currentTime,转而通过时间戳计算精确的剩余时间(带小数的秒数),并缩短进度更新的间隔(比如每16ms,接近屏幕60fps的刷新频率),让进度条能持续平滑变化。

修改后的完整代码

@Composable
fun SmoothTimer(
    totalTime: Long = 10, // 总时长(秒)
    isTimerRunning: Boolean,
    onTimerChange: (Long) -> Unit,
    color: Color = Color.Blue
) {
    // 剩余时间(精确到小数秒)
    var remainingTime by remember { mutableStateOf(totalTime.toFloat()) }
    // 记录暂停时的剩余时间(用于暂停/继续功能)
    var pausedElapsed by remember { mutableFloatStateOf(0f) }

    // 实时计算进度,配合动画让过渡更丝滑
    val progress by animateFloatAsState(
        targetValue = remainingTime / totalTime.toFloat(),
        animationSpec = tween(durationMillis = 16, easing = LinearEasing),
        label = "timer progress"
    )

    CircularProgressIndicator(
        progress = progress,
        modifier = Modifier.size(300.dp),
        color = color,
        strokeWidth = 10.dp,
        strokeCap = StrokeCap.Round,
    )

    LaunchedEffect(isTimerRunning) {
        if (isTimerRunning) {
            val startTimestamp = System.currentTimeMillis() - (pausedElapsed * 1000).toLong()
            while (remainingTime > 0) {
                // 每16ms更新一次,接近60fps,保证视觉流畅
                delay(16)
                val elapsedSeconds = (System.currentTimeMillis() - startTimestamp) / 1000f
                remainingTime = maxOf(totalTime.toFloat() - elapsedSeconds, 0f)
                // 回调整数秒的变化(如果需要)
                onTimerChange(remainingTime.toLong())
            }
            // 计时器结束重置暂停时间
            pausedElapsed = 0f
            onTimerChange(0)
        } else {
            // 暂停时记录当前已流逝的时间
            pausedElapsed = totalTime.toFloat() - remainingTime
        }
    }
}

关键修改点说明

  1. 精确时间计算:
    通过System.currentTimeMillis()记录开始计时的时间戳,实时计算已流逝的秒数(带小数),从而得到精确的剩余时间,避免了整数秒跳变的问题。

  2. 高频更新进度:
    将更新间隔从1000L改为16L,让进度值每帧更新一次,保证视觉上的流畅性。

  3. 动画配合:
    使用animateFloatAsState配合LinearEasing(线性动画),让进度变化更自然,即使因为系统调度有微小延迟,也能通过动画过渡补全。

  4. 暂停/继续支持:
    新增pausedElapsed变量记录暂停时的已流逝时间,实现暂停后继续计时的功能(如果不需要可以去掉这部分逻辑)。

简化版(无需暂停功能)

如果不需要暂停功能,代码可以更简洁:

@Composable
fun SimpleSmoothTimer(
    totalTime: Long = 10,
    isTimerRunning: Boolean,
    onTimerChange: (Long) -> Unit,
    color: Color = Color.Blue
) {
    var remainingTime by remember { mutableStateOf(totalTime.toFloat()) }

    val progress by animateFloatAsState(
        targetValue = remainingTime / totalTime.toFloat(),
        animationSpec = LinearEasing,
        label = "timer progress"
    )

    CircularProgressIndicator(
        progress = progress,
        modifier = Modifier.size(300.dp),
        color = color,
        strokeWidth = 10.dp,
        strokeCap = StrokeCap.Round,
    )

    LaunchedEffect(isTimerRunning) {
        if (isTimerRunning) {
            val start = System.currentTimeMillis()
            while (remainingTime > 0) {
                delay(16)
                val elapsed = (System.currentTimeMillis() - start) / 1000f
                remainingTime = maxOf(totalTime.toFloat() - elapsed, 0f)
                onTimerChange(remainingTime.toLong())
            }
            onTimerChange(0)
        } else {
            remainingTime = totalTime.toFloat()
        }
    }
}

内容的提问来源于stack exchange,提问作者mlzzi

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.19 00:37:40