Compose 有一项规则,即,子项只能测量一次,测量两次就会引发运行时异常。但是,有时需要先收集一些关于子项的信息,然后再测量子项。
借助固有特性,您可以先查询子项,然后再进行实际测量。
对于可组合项,您可以查询其 intrinsicWidth
或 intrinsicHeight
:
(min|max)IntrinsicWidth
:给定此高度,可以正确绘制内容的最小/最大宽度是多少?(min|max)IntrinsicHeight
:给定此宽度,可以正确绘制内容的最小/最大高度是多少?
例如,如果您查询具有无限 width
的 Text
的 minIntrinsicHeight
,它将返回 Text
的 height
,就好像该文本是在单行中绘制的一样。
固有特性的实际运用
假设我们需要创建一个可组合项,该可组合项在屏幕上显示两个用分隔线隔开的文本,如下所示:
我们该怎么做?我们可以将两个 Text
放在同一 Row
,并在其中最大程度地扩展,另外在中间放置一个 Divider
。我们需要将分隔线的高度设置为与最高的 Text
相同,粗细设置为 width = 1.dp
。
@Composable
fun TwoTexts(
text1: String,
text2: String,
modifier: Modifier = Modifier
) {
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
)
}
}
@Preview
@Composable
fun TwoTextsPreview() {
MaterialTheme {
Surface {
TwoTexts(text1 = "Hi", text2 = "there")
}
}
}
预览时,我们发现分隔线扩展到整个屏幕,这并不是我们想要的效果:
之所以出现这种情况,是因为 Row
会逐个测量每个子项,并且 Text
的高度不能用于限制 Divider
。我们希望 Divider
以一个给定的高度来填充可用空间。为此,我们可以使用 height(IntrinsicSize.Min)
修饰符。
height(IntrinsicSize.Min)
可将其子项的高度强行调整为最小固有高度。由于该修饰符具有递归性,因此它将查询 Row
及其子项 minIntrinsicHeight
。
将其应用到代码中,就能达到预期的效果:
@Composable
fun TwoTexts(
text1: String,
text2: String,
modifier: Modifier = Modifier
) {
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
)
}
}
预览如下:
Row
可组合项的 minIntrinsicHeight
将作为其子项的最大 minIntrinsicHeight
。Divider
元素的 minIntrinsicHeight
为 0,因为如果没有给出约束条件,它不会占用任何空间;如果给出特定 width
,Text
minIntrinsicHeight
将为文本的高度。因此,Row
元素的 height
约束条件将为 Text
的最大 minIntrinsicHeight
;而 Divider
会将其 height
扩展为 Row
给定的 height
约束条件。
自定义布局中的固有特性
创建自定义 Layout
或 layout
修饰符时,系统会根据近似值自动计算固有测量结果。因此,计算结果可能并不适用于所有布局。这些 API 提供了替换这些默认值的选项。
若要指定自定义 Layout
的固有特性测量,请在创建该布局时替换 MeasurePolicy
的 minIntrinsicWidth
、minIntrinsicHeight
、maxIntrinsicWidth
和 maxIntrinsicHeight
。
@Composable
fun MyCustomLayout(
content: @Composable () -> Unit,
modifier: Modifier = Modifier
) {
Layout(
modifier = modifier,
content = content,
measurePolicy = object : MeasurePolicy {
override fun MeasureScope.measure(
measurables: List<Measurable>,
constraints: Constraints
): MeasureResult {
// Measure and layout here
}
override fun IntrinsicMeasureScope.maxIntrinsicHeight(
measurables: List<IntrinsicMeasurable>,
width: Int
): Int {
// Logic for calculating custom maxIntrinsicHeight 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.
})