SECRET OF CSS

Adaptive LinearProgressIndicator Height in Compose


Render your Composable the size you want

0*bw9s4PT54XRf8N H
Photo by Sid Balachandran on Unsplash

When building components with Jetpack Compose, you can render them the way you want, thanks to the myriad of Modifier properties.

Most of the time, you can customize any components by overriding this parameter — you should always provide one, too, especially while building an SDK. Arguably, though, some Composables come with a stricter set of rules. That’s the case with the LinearProgressIndicator.

The native SDK exposes a Composable called LinearProgressIndicator that displays a horizontal progress bar. To match the Material guidelines, the height matches LinearIndicatorHeight (which equals to 4.dp).

Here’s the Composable’s implementation (at the time I’m writing this, with Compose 1.2.1):

As you may notice, the Composable internally overrides its size with fixed values.

Of course, this doesn’t prevent you from changing it! Since the Composable naturally exposes a Modifier parameter, you can provide your own size. But what about if your size should fit another Composable’s size?

It’s time to look at the desired output. We would like to draw a progress bar that covers the whole content of a button.

1*TAeyn68uwG38V c1GhJZvQ

If we break this Composable down, we have:

  • a Button
  • a LinearProgressIndicator
  • a Box that encapsulates both the Button and the LinearProgressIndicator

I won’t cover the text and icon within the Button as they are irrelevant.

This brings us to this code:

If you give it a run, you will notice that the progress bar:

  • still has a 4.dp height hence doesn’t fill the button.
  • doesn’t get clipped at the rounded corners. Try it with a red color, you’ll see it straight away.

We can address the second issue easily. One way would be to delegate the shaping to the Box instead of the Button then clip its content.

val shape = RoundedCornerShape(8.dp)
Box(
modifier = modifier
.background(
color = MaterialTheme.colors.primary,
shape = shape,
)
.clip(shape),
)

Let’s move on to the most interesting part. We can pass a hardcoded value to change the progress bar’s height. But we would like it to dynamically match the container height no matter what’s in it.

First, there are several ways to tackle this problem. You could dive into the art of making custom components by using a Layout and sizing the way you desire. For most cases, that would be preferable as it brings the most flexible pattern to draw your component.

Also, you could decide to ditch the LinearProgressIndicator over a Canvas and draw it yourself! Since this progress bar is meant to match the Material guidelines, it might be best not to tweak it further. And this would be one of the simplest shapes to draw in the Canvas. All you need to take care of is the animation part yourself.

Now, for the sake of the example, we will still use our LinearProgressIndicator and achieve the desired result with a few additional lines of code. We need to retrieve the Button height and pass it to the LinearProgressIndicator. We can leverage another useful Modifier called onSizeChanged(). This lambda passes its size when it gets redrawn as an argument. When received, convert it in Dp then remember it so that our LinearProgressIndicator recomposes with its new height.

For completeness, here is the final code of the Composable:

That’s all! This simple technique allows you to resize your components based on other component sizes. Note that you could reuse this pattern for any kind of component, although most of the time, the system should infer their size as you want using Modifier properties.



News Credit

%d bloggers like this: