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.
Get the full code on GitHub.
The building blocks for the canvas are arcs. One will superimpose the other, as shown below.
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.
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 acceptscolor
instead ofbrush
, 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 thefalse
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 of15.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.
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.
...
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.
This is our implementation.
...
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.
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.
Thank you for reading. Let me hear your thoughts in the comments. Until next time, happy coding! 🎨✨
Top comments (0)