Compose 布局中的固有特性测量
Compose 有一项规则:子项只能测量一次,测量两次就会引发运行时异常。但有时需要先收集子项的信息再进行测量,这时可以使用固有特性测量。
什么是固有特性测量
通过固有特性,您可以先查询子项的信息,然后再进行实际测量。对于可组合项,可以查询其 IntrinsicSize.Min 或 IntrinsicSize.Max:
Modifier.width(IntrinsicSize.Min)- 显示内容所需的最小宽度是多少?Modifier.width(IntrinsicSize.Max)- 需要多大的最大宽度才能正确显示内容?Modifier.height(IntrinsicSize.Min)- 显示内容所需的最小高度是多少?Modifier.height(IntrinsicSize.Max)- 需要多大的最大高度才能正确显示内容?
注意:请求固有特性测量不会两次测量子项。系统在测量子项前会先查询其固有测量值,然后父项会根据这些信息计算测量其子项时使用的约束条件。
固有特性的实际运用
以下示例创建一个显示两个用分隔线隔开的文本的可组合项:
@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
)
VerticalDivider(
color = Color.Black,
modifier = Modifier
.fillMaxHeight()
.width(1.dp)
)
Text(
modifier = Modifier
.weight(1f)
.padding(end = 4.dp)
.wrapContentWidth(Alignment.End),
text = text2
)
}
}
在这个初始实现中,Divider 会扩展到整个界面,这不是预期的行为,因为 Row 会逐个测量每个子项,Text 的高度不能用于限制 Divider。
为了解决这个问题,使用 height(IntrinsicSize.Min) 修饰符,它会将子元素的高度调整为最小固有高度:
@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
)
VerticalDivider(
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")
}
}
}
Row 的高度确定方式如下:
- Row 可组合项的
minIntrinsicHeight是其子项的最大minIntrinsicHeight - Divider 元素的
minIntrinsicHeight为 0,因为如果没有给出约束条件,它不会占用任何空间 - Text 的
minIntrinsicHeight是特定 width 的文本 - 因此,Row 元素的 height 约束条件将为 Text 的最大 minIntrinsicHeight
- 然后,Divider 会将其 height 扩展为 Row 给定的 height 约束条件
自定义布局中的固有特性
创建自定义 Layout 或 layout 修饰符时,系统会根据近似值自动计算固有测量结果。这些 API 提供了替换这些默认值的选项。
要指定自定义 Layout 的固有特性测量,需要在创建该布局时替换 MeasurePolicy 接口的相关方法:
@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.
}