如何让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 } } }
关键修改点说明
精确时间计算:
通过System.currentTimeMillis()记录开始计时的时间戳,实时计算已流逝的秒数(带小数),从而得到精确的剩余时间,避免了整数秒跳变的问题。高频更新进度:
将更新间隔从1000L改为16L,让进度值每帧更新一次,保证视觉上的流畅性。动画配合:
使用animateFloatAsState配合LinearEasing(线性动画),让进度变化更自然,即使因为系统调度有微小延迟,也能通过动画过渡补全。暂停/继续支持:
新增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
相关产品推荐
相关产品推荐

