跳转至

SubcomposeLayout

SubcomposeLayout 是什么?

前面固有特性测量部分我们所提到过,固有特性测量的本质其实就是子组件通过父组件对其他子组件产生影响,然而在有些场景下我们不希望父组件参与其中,我们希望通过自定义子组件间测量顺序从而相互影响,Compose为我们提供了 SubcomposeLayout 来处理这类子组件存在依赖关系的场景。

SubcomposeLayout 使用示例

我们仍然使用前面固有特性测量中的例子,通过设计方案的不同可以很清晰的看出固有特性测量与 SubcomposeLayout 的区别。

在这个例子中,其实我们可以先测量两侧文本的高度,并计算出 Divider 应占有的高度然后为 Divider 指定高度后再进行测量,从而达到设计要求。与固有特性测量不同的是,在这整个过程中父组件是没有参与的。

接下里,我们来看看 SubComposeLayout 组件该如何使用。

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

其实 SubComposeLayout 和 Layout 组件用法差不多。不同的是,此时需要传入一个 SubcomposeMeasureScope 类型 Lambda,打开接口声明可以看到其中仅有一个 subcompose API。

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

subcompose 可以将任何 Composable 组件内所有 LayoutNode 转化为一系列 Measurable 测量句柄。接下来我们看看在我们的示例场景中该如何使用。

实际上我们可以把所有待测量的组件分为文字组件和分隔符组件两部分。由于我们的分隔符组件的高度是依赖于文字组件的,所以声明分隔符组件时我们传入一个 Int 值作为测量高度。

首先我们定义一个 Composable。

@Composable
fun SubcomposeRow(
    modifier: Modifier,
    text: @Composable () -> Unit,
    divider: @Composable (Int) -> Unit // 传入高度
){
    SubcomposeLayout(modifier = modifier) { constraints->
        ...
    }
}
我们首先可以使用 subcompose 来先测量 text Composable 中的所有 LayoutNode。并根据测量结果计算出最大高度。

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

既然计算得到了文本的最大高度,我们接下来可以将高度只传入分隔符组件中并进行测量了。

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

值得注意,测量 divider 组件时我们将 minWidth 设置为零,这是由于 constraints 布局约束中宽度可能是固定的,如果不修改的话,divider 组件宽度默认会与整个组件宽度相同。接下来分别对文字组件和分隔符组件进行布局就可以了。

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

使用也非常简单,我们只需要把文本组件和分隔符组件分开传入到我们定制的 SubcomposeRow 组件中就可以了。

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

最终效果与使用固有特性测量完全相同。


最后更新: November 18, 2021