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 returns the height
of the Text
with the text drawn in a single line.
Intrinsics in action
You can create a composable that displays two texts on the screen separated by a divider:
To do this, use a Row
with two Text
composables that fill the available
space, and a Divider
in the middle. The Divider
should be as tall as the
tallest Text
, and it should be 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 ) } }
The Divider
expands to the whole screen, which isn't the desired behavior:
This happens because Row
measures each child individually, and the height of
Text
cannot be used to constrain the Divider
.
To have the Divider
fill the available space with a given height instead,
use the height(IntrinsicSize.Min)
modifier.
height(IntrinsicSize.Min)
sizes its children to be as tall as their minimum
intrinsic height. Because this modifier is recursive, it queries the
minIntrinsicHeight
of the Row
and its children.
Applying this modifier to your code makes it 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
's height is determined as follows:
- The
Row
composable'sminIntrinsicHeight
is the maximumminIntrinsicHeight
of its children. - The
Divider
element'sminIntrinsicHeight
is 0, as it doesn't occupy space if no constraints are given. - The
Text
minIntrinsicHeight
is that of the text for a specificwidth
. - Therefore, the
Row
element'sheight
constraint becomes the maximumminIntrinsicHeight
of theText
s. - The
Divider
then expands itsheight
to theheight
constraint given by theRow
.
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