在Compose中,提到布局,第一个想到的肯定是Modifier。利用Kotlin的扩展函数,像一个解剖刀一样,扩展功能,达到自定义布局的效果。
核心思想
在界面树中布置每个节点的过程分为三个步骤。每个节点必须:
- 测量所有子项
- 确定自己的尺寸
- 放置其子项
核心方法Layout
- Messure Children
- Desice own size
- Place Children
Compose 界面不允许多遍测量。这意味着,布局元素不能为了尝试不同的测量配置而多次测量任何子元素。
如何理解:
只能在测量和布局传递期间测量布局,并且只能在布局传递期间且已进行事先测量之后才能放置子项。由于 Compose 作用域(如 MeasureScope 和 PlacementScope),此操作在编译时强制执行。
LayoutModifier.kt
fun Modifier.layout(
measure: MeasureScope.(Measurable, Constraints) -> MeasureResult
) = this.then(
LayoutModifierImpl(
measureBlock = measure,
inspectorInfo = debugInspectorInfo {
name = "layout"
properties["measure"] = measure
}
)
)
private class LayoutModifierImpl(
val measureBlock: MeasureScope.(Measurable, Constraints) -> MeasureResult,
inspectorInfo: InspectorInfo.() -> Unit,
) : LayoutModifier, InspectorValueInfo(inspectorInfo) {
override fun MeasureScope.measure(
measurable: Measurable,
constraints: Constraints
) = measureBlock(measurable, constraints)
override fun equals(other: Any?): Boolean {
if (this === other) return true
val otherModifier = other as? LayoutModifierImpl ?: return false
return measureBlock == otherModifier.measureBlock
}
override fun hashCode(): Int {
return measureBlock.hashCode()
}
override fun toString(): String {
return "LayoutModifierImpl(measureBlock=$measureBlock)"
}
}
interface MeasureResult {
val width: Int
val height: Int
val alignmentLines: Map<AlignmentLine, Int>
fun placeChildren()
}
官网的例子,麻雀虽小五脏俱全
fun Modifier.firstBaselineToTop(
firstBaselineToTop: Dp
) = layout { measurable, constraints ->
// Measure the composable
val placeable = measurable.measure(constraints)
// Check the composable has a first baseline
check(placeable[FirstBaseline] != AlignmentLine.Unspecified)
val firstBaseline = placeable[FirstBaseline]
// Height of the composable with padding - first baseline
val placeableY = firstBaselineToTop.roundToPx() - firstBaseline
val height = placeable.height + placeableY
layout(placeable.width, height) {
// Where the composable gets placed
placeable.placeRelative(0, placeableY)
}
}
按照这个套路去做简单的自定义布局应该没问题了。
扩展一个组合的自定义布局
@Composable
fun MyBasicColumn(
modifier: Modifier = Modifier,
content: @Composable () -> Unit
) {
Layout(
modifier = modifier,
content = content
) { measurables, constraints ->
// Don't constrain child views further, measure them with given constraints
// List of measured children
val placeables = measurables.map { measurable ->
// Measure each children
measurable.measure(constraints)
}
// Set the size of the layout as big as it can
layout(constraints.maxWidth, constraints.maxHeight) {
// Track the y co-ord we have placed children up to
var yPosition = 0
// Place children in the parent layout
placeables.forEach { placeable ->
// Position item on the screen
placeable.placeRelative(x = 0, y = yPosition)
// Record the y co-ord placed up to
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!")
}
}
传入多个布局参数,然后遍历布局参数,每个布局参数在测量,最后便利安放Children
布局方向
您可以通过更改 [LocalLayoutDirection
]来更改可组合项的布局方向。
如果您要将可组合项手动放置在屏幕上,则 LayoutDirection
是 layout
修饰符或 Layout
可组合项的 LayoutScope
的一部分。
使用 layoutDirection
时,应使用 [place
]放置可组合项。与 [placeRelative
] 方法不同,place
不会根据布局方向(从左到右与从右到左)发生变化。
【⚠️注:layoutDirection与place配合使用,遇到在研究】
网友评论