Skip to content

自定义与高级布局 (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 (自定义布局)

如果您需要布局多个子项(就像 RowColumn 那样),需要使用 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 允许您先测量部分子项,然后根据测量结果来决定下一部分子项的内容或大小。

典型应用BoxWithConstraintsLazyColumn 就是用它实现的。由于性能开销较大,除非必要(如需基于子项大小生成新的子项),否则尽量使用标准 Layout

kotlin