Join the Compose Chinese technology community to get more excellent technical tutorials!

There is currently a Chinese manual project for Jetpack Compose, which aims to help developers better understand and master the Compose framework. This article was written by myself. At present, it has been published in this manual. Welcome to refer to it.

SubcomposeLayout Example

As mentioned in the previous article, the essence of intrinsic property measurement is that the child component influences the other child components through the parent component. However, in some scenarios, we do not want the parent component to be involved. We want to influence each other by customizing the measurement order between the child components. Compose provides us with SubcomposeLayout to handle scenarios where such subcomponents have dependencies.

We still use the previous example of inherent property measurement, and the difference between inherent property measurement and SubcomposeLayout can be clearly seen through different design schemes.

In this example, we can measure the height of the text on both sides, calculate the height that the Divider should occupy, assign the Divider the height, and then measure it to meet the design requirements. Unlike intrinsic property measurement, the parent component is not involved in this whole process.

Next, let’s look at how the SubComposeLayout component is used.

@Composable
fun SubcomposeLayout(
    modifier: Modifier = Modifier,
    measurePolicy: SubcomposeMeasureScope. (Constraints) - >MeasureResult
)
Copy the code

The SubComposeLayout component is similar to the Layout component. The difference is that a SubcomposeMeasureScope type Lambda is passed in. Open the interface declaration and you can see that there is only one Subcompose API.

interface SubcomposeMeasureScope : MeasureScope {
    fun subcompose(slotId: Any? , content: @Composable() - >Unit): List<Measurable>
}
Copy the code

Subcompose converts all layoutNodes in any Composable component into a series of Measurable handles. Let’s look at how this is used in our sample scenario.

In fact, we can divide all the components to be measured into text components and delimiter components. Since the height of our delimiter component is dependent on the literal component, we pass in an Int value to measure the height when we declare the delimiter component.

First we define a Composable.

@Composable
fun SubcomposeRow(
    modifier: Modifier,
    text: @Composable() - >Unit,
    divider: @Composable (Int) - >Unit // Pass height
){ SubcomposeLayout(modifier = modifier) { constraints-> ... }}Copy the code

We can start by using Subcompose to measure all layoutnodes in the Text Composable first. And calculate the maximum height according to the measurement results.

SubcomposeLayout(modifier = modifier) { constraints->
    var maxHeight = 0
    var placeables = subcompose("text", text).map {
        var placeable = it.measure(constraints)
        maxHeight = placeable.height.coerceAtLeast(maxHeight)
        placeable
    }
    ...
}
Copy the code

Now that we have calculated the maximum height of the text, we can pass the height only into the delimiter component and measure it.

SubcomposeLayout(modifier = modifier) { constraints->
    ...
    var dividerPlaceable = subcompose("divider") {
        divider(maxHeight)
    }.map {
        it.measure(constraints.copy(minWidth = 0))
    }
    assert(dividerPlaceable.size == 1, { "DividerScope Error!"})... }Copy the code

It is worth noting that we set minWidth to zero when measuring the Divider component. This is because the width can be fixed in the Constraints of the Constraints layout. If not modified, the divider component defaults to the same width as the entire component. Then lay out the text component and the delimiter component separately.

SubcomposeLayout(modifier = modifier) { constraints->
    ...
    layout(constraints.maxWidth, constraints.maxHeight){
        placeables.forEach {
            it.placeRelative(0, 0)
        }
        dividerPlaceable.forEach {
            it.placeRelative(midPos, 0)
        }
    }
}
Copy the code

It’s easy to use, just pass the text component and the separator component into our custom SubcomposeRow component.

SubcomposeRow(
    modifier = Modifier
        .fillMaxWidth(),
    text = {
        Text(text = "Left", Modifier.wrapContentWidth(Alignment.Start))
        Text(text = "Right", Modifier.wrapContentWidth(Alignment.End))
    }
) {
    var heightPx = with( LocalDensity.current) { it.toDp() }
    Divider(
        color = Color.Black,
        modifier = Modifier
            .width(4.dp)
            .height(heightPx)
    )
}
Copy the code

The end result is exactly the same as using intrinsic property measurements.