Jetpack Compose Draw Arc with Dot Circle

I've looking to draw an arc on a canvas in Jetpack Compose with a little circle on the edge of progress like this picture:

enter image description here

I found how to draw the progress bar with arc canvas but don't know yet how to draw the circle to match with the edge of the arc line. This is my progress code:

@Composable
fun ComposeCircularProgressBar(
    modifier: Modifier = Modifier,
    percentage: Float,
    fillColor: Color,
    backgroundColor: Color,
    strokeWidth: Dp
) {
    Canvas(
        modifier = modifier
            .padding(strokeWidth / 2)
    ) {
        // Background Line
        drawArc(
            color = backgroundColor,
            135f,
            270f,
            false,
            style = Stroke(strokeWidth.toPx(), cap = StrokeCap.Butt)
        )
        // Fill Line
        drawArc(
            color = fillColor,
            135f,
            270f * percentage,
            false,
            style = Stroke(strokeWidth.toPx(), cap = StrokeCap.Round)
        )
    }
}

Noted: for now I know to draw that circle is with Canvas.drawCircle(offset = Offset) but I don't know yet how to calculate the Offset(x,y) to match with the edge of progress.

Upvotes: 16

Views: 6998

Answers (1)

droidchef
droidchef

Reputation: 2307

This piece of code below will generate the arc with the circular dot based on the percentage you provide. You did get most of the part right, it was just about solving the Math Equation to find the point on the circle.

I assumed the radius of the circle as Height / 2 of the widget.

Since we are not drawing the full circle, the start angle is at 140 degrees and the maximum sweep angle is 260 degrees. (I found this by hit and trial, so that it looks as close to your image)

Now to draw the small white circle the center a.k.a offset has to be at (x,y) where x & y are given by the formula

x = radius * sin (angle in radians) y = radius * cos (angle in radians)

@Composable
fun ComposeCircularProgressBar(
    modifier: Modifier = Modifier,
    percentage: Float,
    fillColor: Color,
    backgroundColor: Color,
    strokeWidth: Dp
) {
    Canvas(
        modifier = modifier
            .size(150.dp)
            .padding(10.dp)
    ) {
        // Background Line
        drawArc(
            color = backgroundColor,
            140f,
            260f,
            false,
            style = Stroke(strokeWidth.toPx(), cap = StrokeCap.Round),
            size = Size(size.width, size.height)
        )

        drawArc(
            color = fillColor,
            140f,
             percentage * 260f,
            false,
            style = Stroke(strokeWidth.toPx(), cap = StrokeCap.Round),
            size = Size(size.width, size.height)
        )


        var angleInDegrees = (percentage * 260.0) + 50.0
        var radius = (size.height / 2)
        var x = -(radius * sin(Math.toRadians(angleInDegrees))).toFloat() + (size.width / 2)
        var y = (radius * cos(Math.toRadians(angleInDegrees))).toFloat() + (size.height / 2)

        drawCircle(
            color = Color.White,
            radius = 5f,
            center = Offset(x,  y)
        )
    }
}

Here are some examples I tried with

80% progress

@Preview
@Composable
fun PreviewPorgressBar() {
    ComposeCircularProgressBar(
        percentage = 0.80f,
        fillColor = Color(android.graphics.Color.parseColor("#4DB6AC")),
        backgroundColor = Color(android.graphics.Color.parseColor("#90A4AE")),
        strokeWidth = 10.dp
    )
}

45% progress

@Preview
@Composable
fun PreviewPorgressBar() {
    ComposeCircularProgressBar(
        percentage = 0.45f,
        fillColor = Color(android.graphics.Color.parseColor("#4DB6AC")),
        backgroundColor = Color(android.graphics.Color.parseColor("#90A4AE")),
        strokeWidth = 10.dp
    )
}

100% progress

@Preview
@Composable
fun PreviewPorgressBar() {
    ComposeCircularProgressBar(
        percentage = 1f,
        fillColor = Color(android.graphics.Color.parseColor("#4DB6AC")),
        backgroundColor = Color(android.graphics.Color.parseColor("#90A4AE")),
        strokeWidth = 10.dp
    )
}

[Update] If you're interested in a step-by-step tutorial you can read it here : https://blog.droidchef.dev/custom-progress-with-jetpack-compose-tutorial/

Upvotes: 19

Related Questions