Home > Mobile >  Jetpack Compose Arc Progress Bar Animation (How to restart animation)
Jetpack Compose Arc Progress Bar Animation (How to restart animation)

Time:11-22

How do I create a Arc Progress bar animation like this

animated progress

Currently I've already used Canvas to draw an arc and added animations to the progress bar using animateFloatAsState API. But second pic is not my expected.

[My current implementation]

My current implementation

// e.g. oldScore = 100f  newScore = 350f
// Suppose 250 points are into one level

@Composable
fun ArcProgressbar(
    modifier: Modifier = Modifier,
    oldScore: Float,
    newScore: Float,
    level: String,
    startAngle: Float = 120f,
    limitAngle: Float = 300f,
    thickness: Dp = 8.dp
) {

    var value by remember { mutableStateOf(oldScore) }

    val sweepAngle = animateFloatAsState(
        targetValue = (value / 250) * limitAngle,  // convert the value to angle
        animationSpec = tween(
            durationMillis = 1000
        )
    )

    LaunchedEffect(Unit) {
        delay(1500)
        value = newScore
    }

    Box(modifier = modifier.fillMaxWidth()) {

        Canvas(
            modifier = Modifier
                .fillMaxWidth(0.45f)
                .padding(10.dp)
                .aspectRatio(1f)
                .align(Alignment.Center),
            onDraw = {
                // Background Arc
                drawArc(
                    color = Gray100,
                    startAngle = startAngle,
                    sweepAngle = limitAngle,
                    useCenter = false,
                    style = Stroke(thickness.toPx(), cap = StrokeCap.Square),
                    size = Size(size.width, size.height)
                )

                // Foreground Arc
                drawArc(
                    color = Green500,
                    startAngle = startAngle,
                    sweepAngle = sweepAngle.value,
                    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
        )
    }
}

How can I animate from start again if progress percentage over 100%, just like the one in the gif. Does anybody got some ideas? Thanks!

CodePudding user response:

I made some changes in your code to utilize Animatable so we always snap to the beginning before animating to our target value. We also eliminated the computation here since we just want to fill the entire progress every time the score updates, in our case to 300 (limitAngle) and used the newScore state as a key in the LaunchedEffect to trigger the animation every time it increments. Don't mind the 30 increments, its just an arbitrary value that you can change without affecting the animation.

@Composable
fun ArcProgressbar(
    modifier: Modifier = Modifier,
    newScore: Float,
    level: String,
    startAngle : Float = 120f,
    limitAngle: Float = 300f,
    thickness: Dp = 8.dp
) {

    val animateValue = remember { Animatable(0f) }

    LaunchedEffect(newScore) {
        if (newScore > 0f) {
            animateValue.snapTo(0f)
            delay(10)
            animateValue.animateTo(
                targetValue = limitAngle,
                animationSpec = tween(
                    durationMillis = 1000
                )
            )
        }
    }

    Box(modifier = modifier.fillMaxWidth()) {

        Canvas(
            modifier = Modifier
                .fillMaxWidth(0.45f)
                .padding(10.dp)
                .aspectRatio(1f)
                .align(Alignment.Center),
            onDraw = {
                // Background Arc
                drawArc(
                    color = Color.Gray,
                    startAngle = startAngle,
                    sweepAngle = limitAngle,
                    useCenter = false,
                    style = Stroke(thickness.toPx(), cap = StrokeCap.Square),
                    size = Size(size.width, size.height)
                )

                // Foreground Arc
                drawArc(
                    color = Color.Green,
                    startAngle = startAngle,
                    sweepAngle = animateValue.value,
                    useCenter = false,
                    style = Stroke(thickness.toPx(), cap = StrokeCap.Square),
                    size = Size(size.width, size.height)
                )
            }
        )

        Column {
            Text(
                text = level,
                modifier = Modifier
                    .fillMaxWidth(0.125f)
                    .offset(y = (-10).dp),
                color = Color.Gray,
                fontSize = 82.sp
            )

            Text(
                text = "LEVEL",
                modifier = Modifier
                    .padding(bottom = 8.dp),
                color = Color.Gray,
                fontSize = 20.sp
            )

            Text(
                text = "Score ( $newScore ) ",
                modifier = Modifier
                    .padding(bottom = 8.dp),
                color = Color.Gray,
                fontSize = 20.sp
            )
        }
    }
}

Sample usage:

@Composable
fun ScoreGenerator() {

    var newScore by remember {
        mutableStateOf(0f)
    }

    Column {
        Button(onClick = {
            newScore  = 30f
        }) {
            Text("Add Score   30")
        }

        ArcProgressbar(
            newScore = newScore,
            level = ""
        )
    }
}

enter image description here

  • Related