One of the rules of Compose is that you should only measure your children once; measuring children twice throws a runtime exception. However, there are times when you need some information about your children before measuring them.
Intrinsics lets you query children before they're actually measured.
To a composable, you can ask for its IntrinsicSize.Min
or IntrinsicSize.Max
:
Modifier.width(IntrinsicSize.Min)
- What's the minimum width you need to display your content properly?Modifier.width(IntrinsicSize.Max)
- What's the maximum width you need to display your content properly?Modifier.height(IntrinsicSize.Min)
- What's the minimum height you need to display your content properly?Modifier.height(IntrinsicSize.Max)
- What's the maximum height you need to display your content properly?
For example, if you ask the minIntrinsicHeight
of a Text
with infinite width
constraints in a custom layout, it'll return the height
of the Text
with the text drawn in a single line.
Intrinsics in action
Imagine that we want to create a composable that displays two texts on the screen separated by a divider like this:
How can we do this? We can have a Row
with two Text
s inside that expands as much as they can and a Divider
in the middle. We want the Divider
to be as tall as the tallest Text
and thin (width = 1.dp
).
@Composable fun TwoTexts(modifier: Modifier = Modifier, text1: String, text2: String) { Row(modifier = modifier) { Text( modifier = Modifier .weight(1f) .padding(start = 4.dp) .wrapContentWidth(Alignment.Start), text = text1 ) VerticalDivider( color = Color.Black, modifier = Modifier.fillMaxHeight().width(1.dp) ) Text( modifier = Modifier .weight(1f) .padding(end = 4.dp) .wrapContentWidth(Alignment.End), text = text2 ) } }
If we preview this, we see that the Divider
expands to the whole screen and that's not what we want:
This happens because Row
measures each child individually and the height of Text
cannot be used to constraint the Divider
. We want the Divider
to fill the available space with a given height. For that, we can use the height(IntrinsicSize.Min)
modifier .
height(IntrinsicSize.Min)
sizes its children being forced to be as tall as their minimum intrinsic height. As it's recursive, it'll query Row
and its children minIntrinsicHeight
.
Applying that to our code, it'll work as expected:
@Composable fun TwoTexts(modifier: Modifier = Modifier, text1: String, text2: String) { Row(modifier = modifier.height(IntrinsicSize.Min)) { Text( modifier = Modifier .weight(1f) .padding(start = 4.dp) .wrapContentWidth(Alignment.Start), text = text1 ) VerticalDivider( color = Color.Black, modifier = Modifier.fillMaxHeight().width(1.dp) ) Text( modifier = Modifier .weight(1f) .padding(end = 4.dp) .wrapContentWidth(Alignment.End), text = text2 ) } } // @Preview @Composable fun TwoTextsPreview() { MaterialTheme { Surface { TwoTexts(text1 = "Hi", text2 = "there") } } }
With preview:
The Row
composable's minIntrinsicHeight
will be the maximum minIntrinsicHeight
of its children. The Divider
element's minIntrinsicHeight
is 0 as it doesn't occupy space if no constraints are given; the Text
minIntrinsicHeight
will be that of the text given a specific width
. Therefore, the Row
element's height
constraint will be the max minIntrinsicHeight
of the Text
s. Divider
will then expand its height
to the height
constraint given by the Row
.
Intrinsics in your custom layouts
When creating a custom Layout
or layout
modifier, intrinsic measurements are calculated automatically based on approximations. Therefore, the calculations might not be correct for all layouts. These APIs offer options to override these defaults.
To specify the intrinsics measurements of your custom Layout
, override the minIntrinsicWidth
, minIntrinsicHeight
, maxIntrinsicWidth
, and maxIntrinsicHeight
of the MeasurePolicy
interface when creating it.
@Composable fun MyCustomComposable( modifier: Modifier = Modifier, content: @Composable () -> Unit ) { Layout( content = content, modifier = modifier, measurePolicy = object : MeasurePolicy { override fun MeasureScope.measure( measurables: List<Measurable>, constraints: Constraints ): MeasureResult { // Measure and layout here // ... } override fun IntrinsicMeasureScope.minIntrinsicWidth( measurables: List<IntrinsicMeasurable>, height: Int ): Int { // Logic here // ... } // Other intrinsics related methods have a default value, // you can override only the methods that you need. } ) }
When creating your custom layout
modifier, override the related methods in the LayoutModifier
interface.
fun Modifier.myCustomModifier(/* ... */) = this then object : LayoutModifier { override fun MeasureScope.measure( measurable: Measurable, constraints: Constraints ): MeasureResult { // Measure and layout here // ... } override fun IntrinsicMeasureScope.minIntrinsicWidth( measurable: IntrinsicMeasurable, height: Int ): Int { // Logic here // ... } // Other intrinsics related methods have a default value, // you can override only the methods that you need. }
Recommended for you
- Note: link text is displayed when JavaScript is off
- Custom layouts {:#custom-layouts }
- Alignment lines in Jetpack Compose
- Jetpack Compose Phases