In the last article (in depth Jetpack Compose — Layout principles and Custom Layouts (II)), we explored the nature and principles of Modifier. This time we look at an important feature in the Compose system: the inherent property measurement.
Intrinsic characteristic measurement
Compose, as many of you may already know, has mandated that each widget be measured only once in order to improve its mapping performance. That is, we cannot write code like the following:
val placeables = measurables.map { it.measure(constrains) }
// Try to measure the second time, directly error
val placeablesSecond = measurables.map { it.measure(constrains) }
Copy the code
One quick question
So let’s do a little example. We want to implement a menu with several menu bars. So we write code that looks something like this
But it doesn’t work very well because each Text has a different width. It looks a little ugly
You might say that the solution to this problem is simply to add the modifier fillMaxWidth to each Text and let it fill up. The effect is as follows:
Since the maxWidth of each Text Constraint is the maximum value, the Column width is also the maximum value. So the menu takes up all the screen space. This is not good!
To solve this problem, we simply add such a modifier to Column
Modifier.width(IntrinsicSize.Max)
Copy the code
Its width is the maximum width of the child widget
If there’s Max, there should be Min. Let’s try it. Okay?
The width is getting narrower! Amazing? That’s where the intrinsic property measurement comes in.
(If you’re wondering why the minimum width is this, it’s because a subwidget is text, and the minimum width of text is when it can hold one word per line. In this example, the width of “Send Feedback” is “Send \n Feedback”.
In the example above, Column fits the inherent property measurement. Next, we adapt our own implementation of VerticalLayout (see article 1).
Adaptive inherent property measurement
Let’s turn our attention back to Layout
@Composable inline fun Layout(
content: @Composable() - >Unit,
modifier: Modifier = Modifier,
measurePolicy: MeasurePolicy
)
Copy the code
For the third argument, we wrote it in terms of SAM. Let’s look at MeasurePolicy again
@Stable
fun interface MeasurePolicy {
fun MeasureScope.measure(
measurables: List<Measurable>,
constraints: Constraints
): MeasureResult
/** * The function used to calculate [IntrinsicMeasurable.minIntrinsicWidth]. It represents * the minimum width this layout can take, given a specific height, such that the content * of the layout can be painted correctly. */
fun IntrinsicMeasureScope.minIntrinsicWidth(
measurables: List<IntrinsicMeasurable>,
height: Int
): Int
fun IntrinsicMeasureScope.minIntrinsicHeight(
measurables: List<IntrinsicMeasurable>,
width: Int
): Int
fun IntrinsicMeasureScope.maxIntrinsicWidth(
measurables: List<IntrinsicMeasurable>,
height: Int
): Int
fun IntrinsicMeasureScope.maxIntrinsicHeight(
measurables: List<IntrinsicMeasurable>,
width: Int
): Int
}
Copy the code
The measure method is the one we have used before, and the other extension functions are the ones we need to rewrite to adapt to the inherent property measurement. For example, using modification.width (intrinsicsie.max) calls the maxIntrinsicWidth method, and so on.
Next, let’s get to work. Let’s pick one first
override fun IntrinsicMeasureScope.maxIntrinsicWidth(
measurables: List<IntrinsicMeasurable>,
height: Int
): Int {
TODO("Not yet implemented")}Copy the code
We use the maximum subwidget width as the maximum constraint
override fun IntrinsicMeasureScope.maxIntrinsicWidth(
measurables: List<IntrinsicMeasurable>,
height: Int
): Int {
var width = 0
measurables.forEach {
val childWidth = it.maxIntrinsicWidth(height)
if(childWidth > width) width = childWidth
}
return width
}
Copy the code
The effect is as follows:
The same is true for Min, with the following effect:
The complete code
@Composable
fun VerticalLayoutWithIntrinsic(
modifier: Modifier = Modifier,
content: @Composable() - >Unit
) {
val measurePolicy = object : MeasurePolicy {
override fun MeasureScope.measure(
measurables: List<Measurable>,
constraints: Constraints
): MeasureResult {
val placeables = measurables.map { it.measure(constraints) }
// Width: the widest item
val width = placeables.maxOf { it.width }
// Height: sum of heights of all child widgets
val height = placeables.sumOf { it.height }
return layout(width, height) {
var y = 0
placeables.forEach {
it.placeRelative(0, y)
y += it.height
}
}
}
override fun IntrinsicMeasureScope.maxIntrinsicWidth(
measurables: List<IntrinsicMeasurable>,
height: Int
): Int {
var width = 0
measurables.forEach {
val childWidth = it.maxIntrinsicWidth(height)
if (childWidth > width) width = childWidth
}
return width
}
override fun IntrinsicMeasureScope.minIntrinsicWidth(
measurables: List<IntrinsicMeasurable>,
height: Int
): Int {
var width = Int.MAX_VALUE
measurables.forEach {
val childWidth = it.maxIntrinsicWidth(height)
if (childWidth < width) width = childWidth
}
return width
}
}
Layout(
modifier = modifier,
content = content,
measurePolicy = measurePolicy
)
}
Copy the code
subsequent
So that’s all we’re going to do for the intrinsic property measurements. In the next article, we’ll continue our layout journey by exploring ParentData and other features
Reference for this article:
- For Compose, we have to measure the Intrinsic properties of Compose.
- Deep Dive into Jetpack Compose layouts
All code for this article can be found here