Compose 中的自定义布局
在 Compose 中,界面元素由可组合函数表示,每个元素在界面树中有特定位置和尺寸。布局过程分为三个步骤:测量所有子项、确定自身尺寸、放置子项。
使用布局修饰符
可以使用 layout 修饰符自定义单个元素的测量和布局方式:
fun Modifier.customLayoutModifier() = layout { measurable, constraints ->
// 自定义测量和布局逻辑
}
示例:实现 paddingFromBaseline 修饰符
以下代码实现了一个控制从顶部到第一行文本基线距离的修饰符:
fun Modifier.firstBaselineToTop(firstBaselineToTop: Dp) = layout { measurable, constraints ->
// 测量可组合项
val placeable = measurable.measure(constraints)
// 检查可组合项是否有第一条基线
check(placeable[FirstBaseline] != AlignmentLine.Unspecified)
val firstBaseline = placeable[FirstBaseline]
// 计算可组合项带内边距的高度
val placeableY = firstBaselineToTop.roundToPx() - firstBaseline
val height = placeable.height + placeableY
layout(placeable.width, height) {
// 放置可组合项
placeable.placeRelative(0, placeableY)
}
}
验证代码是否正常工作:
@Preview
@Composable
fun TextWithPaddingToBaselinePreview() {
MyApplicationTheme {
Text("Hi there!", Modifier.firstBaselineToTop(32.dp))
}
}
@Preview
@Composable
fun TextWithNormalPaddingPreview() {
MyApplicationTheme {
Text("Hi there!", Modifier.padding(top = 32.dp))
}
}
创建自定义布局
若要测量和布置多个可组合项,使用 Layout 可组合项:
@Composable
fun MyBasicColumn(
modifier: Modifier = Modifier,
content: @Composable () -> Unit
) {
Layout(
modifier = modifier,
content = content
) { measurables, constraints ->
// 测量和定位子项的逻辑
}
}
实现自定义 Column
以下代码实现了一个基本的垂直布局容器:
@Composable
fun MyBasicColumn(
modifier: Modifier = Modifier,
content: @Composable () -> Unit
) {
Layout(
modifier = modifier,
content = content
) { measurables, constraints ->
// 测量所有子项
val placeables = measurables.map { measurable ->
measurable.measure(constraints)
}
// 设置布局大小
layout(constraints.maxWidth, constraints.maxHeight) {
// 跟踪已放置子项的 y 坐标
var yPosition = 0
// 放置子项
placeables.forEach { placeable ->
placeable.placeRelative(x = 0, y = yPosition)
yPosition += placeable.height
}
}
}
}
使用自定义布局:
@Composable
fun CallingComposable(modifier: Modifier = Modifier) {
MyBasicColumn(modifier.padding(8.dp)) {
Text("MyBasicColumn")
Text("places items")
Text("vertically.")
Text("We've done it by hand!")
}
}
布局方向
可以通过修改 LocalLayoutDirection CompositionLocal 更改布局方向。在手动放置可组合项时,LayoutDirection 是 layout 修饰符或 Layout 可组合项的 LayoutScope 的一部分。
使用 place 而非 placeRelative 可以避免根据布局方向(从左到右或从右到左)自动调整位置:
// 根据布局方向自动调整
placeable.placeRelative(x = 0, y = yPosition)
// 不根据布局方向调整
placeable.place(x = 0, y = yPosition)
重要注意事项
-
单次测量限制:Compose 不允许多次测量同一子项。布局元素不能为了尝试不同配置而多次测量任何子元素。
-
作用域限制:只能在测量和布局传递期间测量布局,且只能在布局传递期间(测量后)放置子项。这些限制由 Compose 作用域(如
MeasureScope和PlacementScope)在编译时强制执行。 -
与 View 系统对比:在 View 系统中,创建自定义布局需要扩展 ViewGroup 并实现测量和布局函数。而在 Compose 中,只需使用
Layout可组合项编写一个函数。
实际应用
要深入了解布局和修饰符,可以参考:
Compose 布局系统提供了灵活而强大的方式来创建自定义 UI 组件,同时保持了代码的简洁性和可维护性。