A custom circular progress indicator using the Canvas and animation
Tags: jetpackcompose, kotlin, android, animation
Published: 2024-03-14 16:18:37

In this post, we will see how to build a custom circular progress indicator using the Canvas. We will be emulating the Samsung Galaxy Watch charging display screen.


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9bivfnoykjl3dpgte1ix.jpg)

Get the full code on [GitHub](https://github.com/tCreates/animChallenge1).

The building blocks for the canvas are arcs. One will superimpose the other, as shown below.


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3htbkz95xq3l8vqk0fwy.png)

The foundational arc will have a grayish background, while the one positioned at the top contains two gradient colors with shades of green. The top arc is the one we will animate to show the charging progress.

### Creating the arcs
We will use the `drawArc` method. Below is the signature of the `drawArc` method.

```java
fun drawArc(
    brush: Brush,
    startAngle: Float,
    sweepAngle: Float,
    useCenter: Boolean,
    topLeft: Offset = Offset.Zero,
    size: Size = this.size.offsetSize(topLeft),
    alpha: @FloatRange(from = 0.0, to = 1.0) Float = 1.0f,
    style: DrawStyle = Fill,
    colorFilter: ColorFilter? = null,
    blendMode: BlendMode = DefaultBlendMode
): Unit
```

We will only be using the `brush`, `startAngle`, `sweepAngle`, `useCenter`,`topLeft`, `size`, and `style` parameters. In a nutshell:

- `brush` is used for setting the color gradient. There is also another variation that accepts `color` instead of `brush`, and that's what we will use for the gray arc.
- `startAngle` for setting the angle the arc will start at
- `sweepAngle` for setting the angle the arc will complete at
- `useCenter` is used to determine if we want our arc to be a sector or an open arc. In our case, we will need it to be open, so we will use the `false` value
- `topLeft` is used to set offsets(displacements of the arc), which in our case we won't need, so our offsets will be zero.
- `size` is for setting the arc's dimensions
- `style` is for setting the arcs styling. For instance, in this case will be setting the endpoints to be round and a width of `15.dp`

Enough with the basics. Let's begin adding the arcs.

For the gray arc, we set it to be a full circle(360°) and let it occupy the full parent's width.

```java
drawArc(
    color=Color(0xFF373a37),
    0f,
    360f,
    topLeft=Offset(x=0.dp.toPx(), y=0.dp.toPx()),
    useCenter=false,
    size=Size(size.width, size.height),
    style=Stroke(15.dp.toPx(), cap=StrokeCap.Round))
```

For the green arc, we will set a gradient, and let it start from -110° to 270°. I will talk about the Android coordinate system in detail in a different post. The degrees choices are based on your preference, the same as the color gradient.

```java
...
val gradient=Brush.verticalGradient(
            colorStops=arrayOf(
            0.0f to Color.Transparent,
            1.0f to Color(0xFF0cdc35)))
...

drawArc(brush=gradient,
    -110f,
    270f,
    topLeft=Offset(x=0.dp.toPx(), y=0.dp.toPx()),
    useCenter=false,
    size=Size(size.width, size.height),
    style=Stroke(15.dp.toPx(), cap=StrokeCap.Round))
```

We are done with the arcs, let's proceed to add the animation.

### Adding the animation (rotating the arc)

We will need to add a rotation animation using the `InfiniteTransition.animateFloat()` method, whose foundational workings are shown in the [documentation](https://developer.android.com/reference/kotlin/androidx/compose/animation/core/InfiniteTransition#(androidx.compose.animation.core.InfiniteTransition).animateFloat(kotlin.Float,kotlin.Float,androidx.compose.animation.core.InfiniteRepeatableSpec,kotlin.String)).

This is our implementation.

```java
...
val infiniteTransition = rememberInfiniteTransition(label = "")
...

val rotation by infiniteTransition.animateFloat(
    initialValue=0f,
    targetValue=360f,
    animationSpec=infiniteRepeatable(
        animation=tween<Float>(durationMillis=1500,
            easing=LinearEasing,
        ),
        repeatMode=RepeatMode.Restart), 
        label="Rotate it"
)
```

We will wrap it around the second arc, as shown below.

```java
rotate(rotation) {
    drawArc(
        //...the rest of the code
    )
}
```

And that's it! We are done with the arcs and the animation. The rest of the components, such as the icon and the text, are in the code I linked above. You can view the running app [here](https://www.instagram.com/p/C4X-lebsBiH/).

Thank you for reading. Let me hear your thoughts in the comments. Until next time, happy coding! 🎨✨