Utilising the Canvas in Jetpack Compose to build a SquareSegmentedProgressIndicator

Odin Asbjørnsen
ProAndroidDev
Published in
5 min readDec 12, 2022

--

In recent years, Jetpack Compose has become more and more popular when making Android UI. The reason mainly being that it simplifies and accelerates UI development. The adoption has also become quite useful when making applications for wearables.

In this article, I will show a practical example of how you can use the Canvas APIs in compose to make a square segmented progress indicator (SSPI) perfectly fitting for square-shaped wearables.

Photo by Justyn Warner on Unsplash
Photo by Justyn Warner on Unsplash

Canvas in Compose?

The Canvas in Jetpack Compose is a Composable function that wraps the native canvas APIs from the UI toolkit. This means you can easily integrate a canvas into your composable layout code by just calling the canvas composable. If you want to know more about canvas in Compose, I would recommend checking out this article which goes quite deeply into how it works.

What are we going to build?

In this article, we are going to build a square-segmented progress indicator (SSPI). Yeah I know, it’s a mouthful! The main idea is to make a composable that is indicating a progress of some sort. It should fulfil these criteria:

  • The API should take a list of segments that can have different colour and weight.
  • It should get a track colour to paint the track behind.
  • It should be able to adjust the corner radius, stroke-width and progress.
  • The padding between the segments should be adjustable.

This should result in something like this:

Caption of the square segmented progress indicators

This can also be extended with a cool animation, indicating a progress going in this example from 0% to 100%.

SSPI animation
Square segmented progress indicator animation GIF

As you can see, here the user has provided 3 track segments with the same weight and different colour.

Where do we start?

Initially, we need to implement a function that uses the Canvas composable. My initial thought was that since I knew there existed a drawRoundedRect extension function on the drawScope that we could use that. However, after a lot of googling, I found that because we need to implement different segments, drawRoundedRect would not work. The reason is that by usingdrawRoundedRect we can’t just draw parts of the square.

This means that we have to utilise the drawLine anddrawArch extension functions to make this. We have to make an algorithm to calculate when to draw the different components of the rectangle. Then we have to first draw the track segment, and then draw the progress segment on top of this.

We can use the drawPath API from Canvas to draw a set of individual paths. Then we can just draw the path that the list of segments contains. Using drawPath also makes sure that the line ends and arch ends are lined up correctly.

In the above code, we have added the Canvas composable with the progress provided by the user. We are then looping through the calculated segments and drawing first the track path, then the progress path.

These segment groups are calculated based on the list of indicator segments that the user provides. It also takes into account the height/width of the canvas and the provided corner radius.

This returns a list of the segment groups that each contain a set of segments. These are calculated using the width, height and corner radii of the canvas. We need this to know where the different sections are going to be defined. This is helpful information when we are going to draw a straight horizontal line, a straight vertical line or an arch.

After defining where the sections should be we can then calculate the segmented sections and add them to their segment group.

Here we then check if the range is intersecting with the points that we have calculated above. If it is, we can add a calculated segment to the list. This calculated segment contains a segment drawable characteristic for each segment depending on the type of path we want to draw together with its track colour, indicator colour and the section range.

This then makes the path that is drawn when calling the drawCompleted/drawIncompleted functions on the CalculatedSegment class.

If you want to check out the full code for this, you can find the implementation here.

Let's make a cool animation!

SSPI animation
Square segmented progress indicator animation GIF

After making the SSPI composable, it leaves us with an API like this

By using the preview tooling in Android Studio, we can add a preview to check this out.

This will make a basic first preview catered to a square Wear OS device. If you want to use the @WearSquareDevicePreview custom annotation, you can get it from the horologist project or make your own.

Since the API exposes the progress, it makes a great variable for animation. We can use this to make a cool animation where the state starts from 0f representing 0% to 1f representing 100%. We can then use the update transition API and the animateFloat extension to update the progress based on a tween with LinearEasing . This will make the progress go from 0 to 100 in a linear duration of time.

After drawing our SSPI with the animation, we can add a Canvas on top of the function. Here we can draw horizontal and vertical lines to get reference points through the animation. This will result in something like this

Square segmented progress indicator with progress animation on a square Wear device.
Square segmented progress indicator with animation in progress
Square segmented progress indicator with progress animation 68% on a square Wear OS device.
SSPI animation
Square segmented progress indicator animation GIF

Wrap it up 🌯

I think this component looks great on a square Wear OS device! If you would like to check the source code for this article, you can find it here.

When making this I got to experience the beauty of making Compose code, and interacting with the canvas APIs. I also think the preview tooling of animations are amazing. Here you can inspect the animation in detail which was super helpful!

Furthermore, if you are interested in Wear OS development, I highly recommend checking out the Horologist project. It is truly an amazing asset when working with Wear OS.

Helpful resources

Thanks for reading this article! I hope this helps somebody!

I would also like to thank Yuri Schimke for all the help, inspiration, review and mentorship! Furthermore, I would also like to thank Ataul Munim for great review! You’re the best! 🙏

--

--