Jetpack Compose弧形进度条动画:进度超100%时如何重启?
解决弧形进度条跨等级循环动画问题
要实现分数超过250分(一级)时进度条填满一圈后从头开始动画的效果,核心是将动画拆分为填满当前等级和绘制剩余分数两个阶段,使用Animatable来手动控制多阶段动画序列。以下是修改后的完整实现:
修改后的代码
// e.g. oldScore = 100f newScore = 350f // Suppose 250 points are into one level import androidx.compose.animation.core.Animatable import androidx.compose.animation.core.tween import androidx.compose.foundation.Canvas import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.aspectRatio import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.geometry.Size import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.StrokeCap import androidx.compose.ui.graphics.drawscope.Stroke import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp // 假设的颜色常量,根据你的项目替换 val Gray100 = Color(0xFFF3F4F6) val Green500 = Color(0xFF22C55E) @Composable fun ArcProgressbar( modifier: Modifier = Modifier, oldScore: Float, newScore: Float, level: String, startAngle: Float = 120f, limitAngle: Float = 300f, thickness: Dp = 8.dp ) { // 使用Animatable控制分数动画,支持多阶段序列操作 val animatableScore = remember { Animatable(oldScore) } LaunchedEffect(newScore) { // 反向分数变化直接动画 if (newScore <= oldScore) { animatableScore.animateTo( targetValue = newScore, animationSpec = tween(durationMillis = 1000) ) return@LaunchedEffect } // 计算完整等级数和剩余分数 val targetFullLevels = (newScore / 250).toInt() val remainingScore = newScore % 250 // 先动画填满当前未完成的等级 if (oldScore < targetFullLevels * 250f) { animatableScore.animateTo( targetValue = targetFullLevels * 250f, animationSpec = tween(durationMillis = 1000) ) // 重置动画起点到0,准备下一轮绘制 animatableScore.snapTo(0f) } // 动画剩余分数对应的进度 if (remainingScore > 0) { animatableScore.animateTo( targetValue = remainingScore, animationSpec = tween(durationMillis = 1000) ) } } // 将当前动画分数转换为进度条扫过角度 val sweepAngle = (animatableScore.value / 250) * limitAngle Box(modifier = modifier.fillMaxWidth()) { Canvas( modifier = Modifier .fillMaxWidth(0.45f) .padding(10.dp) .aspectRatio(1f) .align(Alignment.Center), onDraw = { // 背景弧形 drawArc( color = Gray100, startAngle = startAngle, sweepAngle = limitAngle, useCenter = false, style = Stroke(thickness.toPx(), cap = StrokeCap.Square), size = Size(size.width, size.height) ) // 前景进度弧形 drawArc( color = Green500, startAngle = startAngle, sweepAngle = sweepAngle, useCenter = false, style = Stroke(thickness.toPx(), cap = StrokeCap.Square), size = Size(size.width, size.height) ) } ) Text( text = level, modifier = Modifier .fillMaxWidth(0.125f) .align(Alignment.Center) .offset(y = (-10).dp), color = Color.White, fontSize = 82.sp ) Text( text = "LEVEL", modifier = Modifier .padding(bottom = 8.dp) .align(Alignment.BottomCenter), color = Color.White, fontSize = 20.sp ) } }
关键修改说明
- 替换动画API:用
Animatable替代原有的mutableStateOf+animateFloatAsState,支持手动控制多阶段动画序列,满足循环绘制需求。 - 分阶段动画逻辑:
- 先将进度条动画填满当前未完成的等级(如从100分动画到250分);
- 通过
snapTo(0f)重置动画起点,让进度条回到初始位置; - 再动画剩余分数对应的进度(如350-250=100分,从0开始绘制到100分对应的角度)。
- 多等级适配:自动处理多次跨等级的情况(如从100分到700分,会完成两次填满->重置->动画的循环)。
内容的提问来源于stack exchange,提问作者Claire
相关产品推荐
相关产品推荐

