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