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

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,支持手动控制多阶段动画序列,满足循环绘制需求。
  • 分阶段动画逻辑:
    1. 先将进度条动画填满当前未完成的等级(如从100分动画到250分);
    2. 通过snapTo(0f)重置动画起点,让进度条回到初始位置;
    3. 再动画剩余分数对应的进度(如350-250=100分,从0开始绘制到100分对应的角度)。
  • 多等级适配:自动处理多次跨等级的情况(如从100分到700分,会完成两次填满->重置->动画的循环)。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.11 07:35:35