A glowing progress ring with rounded ends for Android

Andréas Saudemont
Glose Engineering
Published in
4 min readAug 10, 2020

--

How we created a custom Android view which draws a glowing progress ring with rounded ends.

With Glose you can set a goal of reading a given number of pages each day. You can then follow your daily progress with the daily goal widget on the home screen:

When you haven’t reached your daily goal yet
When you’ve reached or exceeded your daily goal

The first implementation of the widget used a circular ProgressBar to represent the progress ring. This did the job but missed a few features we wanted:

  1. The ends of the progress ring should be rounded. With the circular ProgressBar the ends are flat and do not match the overall style of the app.
  2. We should be able to use any drawable for the progress ring, like a gradient for instance, and not be limited to a solid color.
  3. The progress ring should project a glow when you’ve exceeded your goal. The more the goal has been exceeded, the bigger the glow effect.

With these features in mind, we set out to update our implementation of the daily goal widget.

Rounding the ends

We couldn’t find a way to create a drawable for the ProgressBar that would draw rounded ends on the progress ring. So we replaced the ProgressBar with a custom view inspired by this StackOverflow answer.

This custom view sets up the two Paint objects that will be used to draw the ring:

ringPaint is configured with the ROUND stroke cap, which is how the progress ring will get its ends rounded.

The drawing of the ring is then performed by the onDraw() override using simple drawArc() primitives:

arcRect is a RectF that caches the bounds of the arc used to draw the ring. It is updated each time onSizeChanged() is called:

And that’s all it takes to get nice rounded ends:

Look ma, rounded ends!

Using a drawable for the progress ring

The progress ring is also used in a popup panel showing historical data. In this popup the ring must be drawn using a gradient that is lighter at the top and darker at the bottom.

To achieve this, we’ve updated the onDraw() method so that the arc it draws acts as a mask on top of the ring drawable:

The key here is ringMaskPaint, which is used as the paint for the second Canvas.saveLayer() call. It is configured with the PorterDuff.Mode.MULTIPLY transfer mode, and as a result anything that is drawn in this layer (the progress ring) will act as a mask over what is drawn in the layer below (the ring drawable). See Porter/Duff Compositing and Blend Modes by Søren Sandmann Pedersen for an illustrated explanation of these concepts.

The onSizeChanged() method is also modified to adjust the bounds of ringDrawable appropriately:

And here is the result:

Using a gradient to draw the ring

Adding the glow effect

The progress ring is expected to glow when you’ve exceeded your reading goal, and the more you’ve exceeded your goal, the more the ring glows. We achieve this in the onDraw() override by drawing an arc with a Paint object configured to draw a shadow layer below the arc. We’ve also added the following attributes on the custom view:

  • inset defines the dimension by which the progress ring is inset inside the view bounds to leave space for the glow effect.
  • glowColor is the color used to draw the glow effect.
  • glowPeak is the ratio beyond maxProgress where the glow effect reaches its peak.

And that is what it looks like in the daily goal widget:

The ✨ glow ✨

Animating the ring and putting it all together

Lastly, we added a setProgressAnimated() method on the custom view that we use to animate the progress ring from zero to its current value. This uses a ValueAnimator that updates the progress value from zero to the desired value. The duration of the animation is adjusted so that the progress ring grows approximately at the same speed across the progress value spectrum.

This is how the progress ring is animated in the popup panel:

And that is how it is animated in the daily goal widget:

Here’s the same animation in slow-motion:

Curious about what it looks like in real life? Go ahead and install Glose for Android, and let us know what you think!

--

--