Compose 布局中的内在测量

Compose 的规则之一是您应该只测量您的子项一次;测量子项两次会抛出运行时异常。但是,有时您需要在测量子项之前获取有关子项的一些信息。

内在测量让您可以在子项实际测量之前查询它们。

对于一个可组合组件,您可以询问它的intrinsicWidthintrinsicHeight

  • (min|max)IntrinsicWidth:给定这个宽度,您能以什么最小/最大宽度正确地绘制您的内容?
  • (min|max)IntrinsicHeight:给定这个高度,您能以什么最小/最大高度正确地绘制您的内容?

例如,如果您询问具有无限heightTextminIntrinsicHeight,它将返回Textheight,就像文本在一行中绘制一样。

内在测量在实际中的应用

假设我们想要创建一个可组合组件,它在屏幕上显示两个文本,中间用分隔线隔开,如下所示

Two text elements side by side, with a vertical divider between them

我们该如何实现?我们可以使用一个包含两个TextRow,它们尽可能地扩展,并在中间添加一个Divider。我们希望Divider 与最高的Text 一样高,并且很薄 (width = 1.dp)。

@Composable
fun TwoTexts(modifier: Modifier = Modifier, text1: String, text2: String) {
    Row(modifier = modifier) {
        Text(
            modifier = Modifier
                .weight(1f)
                .padding(start = 4.dp)
                .wrapContentWidth(Alignment.Start),
            text = text1
        )
        Divider(
            color = Color.Black,
            modifier = Modifier.fillMaxHeight().width(1.dp)
        )
        Text(
            modifier = Modifier
                .weight(1f)
                .padding(end = 4.dp)
                .wrapContentWidth(Alignment.End),

            text = text2
        )
    }
}

如果我们预览它,我们会看到Divider 扩展到整个屏幕,这不是我们想要的。

Two text elements side by side, with a divider between them, but the divider stretches down below the bottom of the text

之所以会这样,是因为Row 会分别测量每个子元素,而Text 的高度无法用来限制Divider。我们希望Divider 使用给定的高度填充可用空间。为此,我们可以使用height(IntrinsicSize.Min) 修饰符。

height(IntrinsicSize.Min) 会将它的子元素的大小设置为它们的最小内在高度。由于它是递归的,它将查询Row 及其子元素的minIntrinsicHeight

将其应用到我们的代码中,它将按预期工作。

@Composable
fun TwoTexts(modifier: Modifier = Modifier, text1: String, text2: String) {
    Row(modifier = modifier.height(IntrinsicSize.Min)) {
        Text(
            modifier = Modifier
                .weight(1f)
                .padding(start = 4.dp)
                .wrapContentWidth(Alignment.Start),
            text = text1
        )
        Divider(
            color = Color.Black,
            modifier = Modifier.fillMaxHeight().width(1.dp)
        )
        Text(
            modifier = Modifier
                .weight(1f)
                .padding(end = 4.dp)
                .wrapContentWidth(Alignment.End),

            text = text2
        )
    }
}

// @Preview
@Composable
fun TwoTextsPreview() {
    MaterialTheme {
        Surface {
            TwoTexts(text1 = "Hi", text2 = "there")
        }
    }
}

带有预览

Two text elements side by side, with a vertical divider between them

Row 可组合组件的minIntrinsicHeight 将是其子元素的最大minIntrinsicHeightDivider 元素的minIntrinsicHeight 为 0,因为它在没有给定约束条件的情况下不会占用空间;TextminIntrinsicHeight 将是给定特定width 的文本的minIntrinsicHeight。因此,Row 元素的height 约束将是Text 的最大minIntrinsicHeightDivider 然后将它的height 扩展到由Row 给定的height 约束。

在自定义布局中使用内在测量

在创建自定义Layoutlayout 修饰符时,内在测量值会根据近似值自动计算。因此,计算结果可能不适用于所有布局。这些 API 提供了覆盖这些默认值的选项。

要指定自定义Layout 的内在测量值,请在创建时覆盖MeasurePolicy 接口的minIntrinsicWidthminIntrinsicHeightmaxIntrinsicWidthmaxIntrinsicHeight

@Composable
fun MyCustomComposable(
    modifier: Modifier = Modifier,
    content: @Composable () -> Unit
) {
    Layout(
        content = content,
        modifier = modifier,
        measurePolicy = object : MeasurePolicy {
            override fun MeasureScope.measure(
                measurables: List<Measurable>,
                constraints: Constraints
            ): MeasureResult {
                // Measure and layout here
                // ...
            }

            override fun IntrinsicMeasureScope.minIntrinsicWidth(
                measurables: List<IntrinsicMeasurable>,
                height: Int
            ): Int {
                // Logic here
                // ...
            }

            // Other intrinsics related methods have a default value,
            // you can override only the methods that you need.
        }
    )
}

在创建自定义layout 修饰符时,请覆盖LayoutModifier 接口中的相关方法。

fun Modifier.myCustomModifier(/* ... */) = this then object : LayoutModifier {

    override fun MeasureScope.measure(
        measurable: Measurable,
        constraints: Constraints
    ): MeasureResult {
        // Measure and layout here
        // ...
    }

    override fun IntrinsicMeasureScope.minIntrinsicWidth(
        measurable: IntrinsicMeasurable,
        height: Int
    ): Int {
        // Logic here
        // ...
    }

    // Other intrinsics related methods have a default value,
    // you can override only the methods that you need.
}