In depth Jetpack Compose - layout principle and custom layout

Posted by smonsivaes on Mon, 28 Feb 2022 03:34:39 +0100

In the last article( In depth jetpack composition - layout principle and custom layout (II) In, we explored the essence and principle of Modifier. This time, let's look at an important feature in the Compose system: inherent characteristic measurement.

Inherent characteristic measurement

Perhaps many people already know that in order to improve the mapping performance, Compose forcibly stipulates that each widget can only be measured once. In other words, we can't write code like this:

val placeables = measurables.map { it.measure(constrains) }
// Try to measure the second time and report the error directly
val placeablesSecond = measurables.map { it.measure(constrains) }

A small problem

Then let's look at a small example. We want to implement a menu with several menu bars. So we wrote code like this

But the effect is not good, because the width of each Text is different. It looks a little ugly

You might say that it's easy to solve this problem by adding the modifier fillMaxWidth to each Text and making it full. The effect is as follows:

But here comes a new problem: since the maxWidth of each Text Constraint is the maximum, our Column width is also the maximum. So this menu takes up all the screen space. That's not good!

To solve this problem, we only need to add such a modifier to the Column

Modifier.width(IntrinsicSize.Max)

Its width is the maximum width of the sub widget

If there is Max, there should be Min. let's try it?

The width has narrowed! Is it amazing? This is the credit of inherent characteristic measurement.

(if you are curious why the minimum width is this, because the widget is text, and the minimum width of the text is the width when it can hold one word per line. In this example, it is the width of the line of feed when Send Feedback is divided into Send \n Feedback.)

In the above example, Column is adapted to the inherent characteristic measurement. Next, let's adapt our own implementation of vertical layout (see Chapter 1 for the specific implementation of vertical layout).

Adaptive natural characteristic measurement

Let's turn our attention to Layout again

@Composable inline fun Layout(
    content: @Composable () -> Unit,
    modifier: Modifier = Modifier,
    measurePolicy: MeasurePolicy
)

Previously, for the third parameter, we wrote it in the form of SAM. Now let's take a look at the MeasurePolicy

@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
}

We have used the measure method before, and the other expansion functions are what we need to rewrite to adapt to the measurement of inherent characteristics. For example, use modifier Width (intrinsicsize. Max), the maxIntrinsicWidth method will be called, and the rest are the same.

Next, let's do it. Pick one first

override fun IntrinsicMeasureScope.maxIntrinsicWidth(
    measurables: List<IntrinsicMeasurable>,
    height: Int
): Int {
    TODO("Not yet implemented")
}

We take the maximum width of the sub widget 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
}

The effect is as follows:

(width is based on the word Funny)

min is similar, and the effect is as follows:

(width is based on the word is)

See Github warehouse for complete code

follow-up

Let's look at these first about the measurement of inherent characteristics. In the next article, we will explore ParentData and other features to continue our layout journey

Reference:

For all codes in this article, see: here

Topics: Android