自定义与高级布局 (Custom)
源:自定义布局
当标准布局 (Row, Column, Box) 无法满足需求时,Compose 提供了强大的自定义布局能力。
1. 响应式布局 (BoxWithConstraints)
普通的 Box 不知道自己的可用空间大小,而 BoxWithConstraints 可以根据父容器给出的约束(Constraints)来决定显示内容。
适用场景
根据屏幕宽度决定显示单栏还是双栏布局,或者根据高度决定是否隐藏某些元素。
kotlin
BoxWithConstraints {
// 作用域内可以直接访问 maxWidth, maxHeight, minWidth 等属性
if (maxWidth < 400.dp) {
Column {
Image(...)
Text(...)
}
} else {
Row {
Image(...)
Text(...)
}
}
}2. layout 修饰符
如果您只需要修改单个元素的测量和布局行为,可以使用 Modifier.layout。
kotlin
fun Modifier.firstBaselineToTop(
firstBaselineToTop: Dp
) = this.then(
layout { measurable, constraints ->
// 1. 测量元素
val placeable = measurable.measure(constraints)
// 2. 检查基线是否存在
check(placeable[FirstBaseline] != AlignmentLine.Unspecified)
val firstBaseline = placeable[FirstBaseline]
// 3. 计算新的 Y 坐标和高度
val placeableY = firstBaselineToTop.roundToPx() - firstBaseline
val height = placeable.height + placeableY
// 4. 指定布局大小并放置
layout(placeable.width, height) {
placeable.placeRelative(0, placeableY)
}
}
)3. Layout Composable (自定义布局)
如果您需要布局多个子项(就像 Row 或 Column 那样),需要使用 Layout 函数。
kotlin
@Composable
fun MyCustomColumn(
modifier: Modifier = Modifier,
content: @Composable () -> Unit
) {
Layout(
modifier = modifier,
content = content
) { measurables, constraints ->
// 1. 测量所有子项
// 这里只是简单地使用最大约束进行测量,实际逻辑可能更复杂
val placeables = measurables.map { measurable ->
measurable.measure(constraints)
}
// 2. 计算自身的大小
// 宽度取最宽子项,高度取所有子项高度之和
val layoutWidth = placeables.maxOfOrNull { it.width } ?: 0
val layoutHeight = placeables.sumOf { it.height }
// 3. 布局子项
layout(layoutWidth, layoutHeight) {
var yPosition = 0
placeables.forEach { placeable ->
placeable.placeRelative(x = 0, y = yPosition)
yPosition += placeable.height
}
}
}
}kotlin
MyCustomColumn {
Text("Child 1")
Text("Child 2")
}4. 固有特性测量 (Intrinsic Measurements)
有时,子项需要知道父容器在“不限制大小”下的理想大小,或者子项之间需要高度同步(例如:让一行中的所有元素高度一致,取最高者的值)。
常用场景
在 Row 中添加一个垂直分割线,要求分割线高度与最高的文本高度一致。默认情况下 Row 测量每个子项是独立的,分割线不知道文本有多高,使用 IntrinsicSize.Min 可以解决这个问题。
kotlin
// 让 Row 的高度等于其子项中 最小的固有高度(即“刚好包裹内容”的高度)
Row(modifier = Modifier.height(IntrinsicSize.Min)) {
Text(
text = "左侧\n文本\n更高",
modifier = Modifier.weight(1f).padding(8.dp)
)
// 分割线将自动填满 Row 的高度,即与左侧文本高度一致
VerticalDivider(color = Color.Black)
Text(
text = "右侧",
modifier = Modifier.weight(1f).padding(8.dp)
)
}5. SubcomposeLayout (子组合布局)
这是最高级的布局 API。标准的 Layout 需要一次性测量所有子项。而 SubcomposeLayout 允许您先测量部分子项,然后根据测量结果来决定下一部分子项的内容或大小。
典型应用:BoxWithConstraints 和 LazyColumn 就是用它实现的。由于性能开销较大,除非必要(如需基于子项大小生成新的子项),否则尽量使用标准 Layout。
kotlin