Square size in Jetpack Compose
Creating a custom layout modifier to adjust size dynamically
«In the middle of every difficulty lies opportunity.» — Albert Einstein
Problem
You are building a piece of UI that contains a button with text. The requirement for the button is to be perfectly round. It’s clear that the deciding factor here is the size of the button’s content.
How do we make a @Composable
square?
Solution with fixed size
The easiest and the most obvious solution is to hardcode some good-looking size with Modifier.size(48.dp)
. It will not only make button’s content square, but will also ensure that all buttons have same exact size.
While this approach is very efficient, it requires manual maintenance. You’ll need to adjust specified size if the content changes, e.g. fontSize
increases, e.g. user changes font size on their device.
Solution with dynamic size
The approach with fixed size has a downside of being bug-prone due to necessity of manual maintenance. To eliminate this nuance we should base the size of a square on the size of the content. If the content is measured as 100x40
, we should make it a 100x100
square by extending the smallest measurement.
Attempt #1: aspectRatio()
modifier
After taking a look at the List of Compose modifiers you may give aspectRatio(1f)
a try. Let’s see how it goes.
Unfortunately, it goes not as one may expect. Our Text()
explodes and takes all available space, though it’s square as we planned. The reason for such behaviour is implementation of aspectRatio()
. It tries to match max incoming constraints, and since in our case they are limited only by device size, the text grows accordingly.
You can add sizeIn(maxWidth = 48.dp, maxHeight = 48.dp)
before aspectRatio()
to limit incoming constraints. However, in the essence it is just the same as solution with fixed size.
I wasn’t able to solve the problem using any out-of-the-box modifiers or their combination. Write a comment if you know how!
Attempt #2: custom layout modifier
When it is necessary to define a way of measuring and/or positioning, Compose provides LayoutModifier
. That’s what we’re going to use for our implementation.
Before starting to write code, let’s decide on the algorithm.
- First, we limit incoming max constraints.
If the content is measured with initial constraints straight away then in some cases we won’t be able to make the content of square size without a) cropping the content or b) getting out of constraints’ bounds.
Max constraints are limited to the largest possible square. This way the content won’t exceed initial incoming constraints when we make it square. - Next, we decide on layout size based on largest dimension of the measured content.
By doing so we’re making sure that the other dimension doesn’t get clipped. - Finally, we position the content in resulting square layout.
In my implementation I useposition: Float
parameter to specify the place of the content inside the square.
Additionally, if it’s impossible to fit a square within the incoming constraints, the modifier should be ignored and the content should be laid out as if the modifier was not applied.
And here’s the implementation:
Happy coding!