借助 Compose 布局模型,您可以使用 AlignmentLine
创建自定义对齐线,供父布局用来对齐和定位其子项。例如,Row
可以使用其子项的自定义对齐线来对齐子项。
当布局为特定 AlignmentLine
提供值时,该布局的父项可以在测量后读取该值,同时对相应的 Placeable
实例使用 Placeable.get
运算符。然后,父级可以根据 AlignmentLine
的位置确定子级的位置。
Compose 中的某些可组合项已附带对齐线。例如,BasicText
可组合项会公开 FirstBaseline
和 LastBaseline
对齐线。
在以下示例中,名为 firstBaselineToTop
的自定义 LayoutModifier
会读取 FirstBaseline
,向 Text
添加自其第一条基线起的内边距。
图 1. 显示了向元素添加正常的内边距与将内边距应用到文本元素的基线之间的差异。
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) } } @Preview @Composable fun TextWithPaddingToBaselinePreview() { MaterialTheme { Text("Hi there!", Modifier.firstBaselineToTop(32.dp)) } }
在此示例中,为了读取 FirstBaseline
,我们在测量阶段使用了 placeable [FirstBaseline]
。
创建自定义对齐线
创建自定义 Layout
可组合项或自定义 LayoutModifier
时,您可以提供自定义对齐线,以便其他父级可组合项可以用它们来相应地对齐和定位其子项。
以下示例展示了一个自定义 BarChart
可组合项,它公开了两条对齐线:MaxChartValue
和 MinChartValue
;这样,其他可组合项就可以对齐到图表的最大和最小数据值。两个文本元素“Max”和“Min”已与自定义对齐线的中心对齐。
图 2. BarChart
可组合项,其中的文本元素与最大和最小数据值对齐。
自定义对齐线被定义为项目中的顶级变量。
import kotlin.math.max
import kotlin.math.min
import androidx.compose.ui.layout.HorizontalAlignmentLine
/**
* AlignmentLine defined by the maximum data value in a [BarChart]
*/
val MaxChartValue = HorizontalAlignmentLine(merger = { old, new -> min(old, new) })
/**
* AlignmentLine defined by the minimum data value in a [BarChart]
*/
val MinChartValue = HorizontalAlignmentLine(merger = { old, new -> max(old, new) })
我们的示例中用到的自定义对齐线属于 HorizontalAlignmentLine
类型,因为它们用于垂直对齐子项。如果多个布局为这些对齐线提供了一个值,系统就会以参数的形式传递合并政策。由于 Compose 布局系统坐标和 Canvas
坐标代表 [0, 0]
,左上角以及 x
轴和 y
轴的正方向都是向下的,因此 MaxChartValue
值将始终小于 MinChartValue
。因此,对于最大图表数据值基线,合并策略为 min
;对于最小图表数据值基线,合并策略为 max
。
创建自定义 Layout
或 LayoutModifier
时,请在接受 alignmentLines: Map<AlignmentLine, Int>
参数的 MeasureScope.layout
方法中指定自定义对齐线。
@Composable
fun BarChart(
dataPoints: List<Int>,
modifier: Modifier = Modifier
) {
/* ... */
BoxWithConstraints(modifier = modifier) {
// Calculate custom AlignmentLines: minYBaseline and maxYBaseline
val maxYBaseline = /* ... */
val minYBaseline = /* ... */
Layout(
content = {},
modifier = Modifier.drawBehind {
// Logic to draw the Chart
}
) { _, constraints ->
with(constraints) {
layout(
width = if (hasBoundedWidth) maxWidth else minWidth,
height = if (hasBoundedHeight) maxHeight else minHeight,
// Custom AlignmentLines are set here. These are propagated
// to direct and indirect parent composables.
alignmentLines = mapOf(
MinChartValue to minYBaseline.roundToInt(),
MaxChartValue to maxYBaseline.roundToInt()
)
) {}
}
}
}
}
此可组合项的直接和间接父级均可使用对齐线。以下可组合项会创建一个自定义布局,它将两个 Text
槽和数据点作为参数,并将两个文本与最大和最小图表数据值对齐。这个可组合项的预览如图 2 所示。
@Composable
fun BarChartMinMax(
dataPoints: List<Int>,
maxText: @Composable () -> Unit,
minText: @Composable () -> Unit,
modifier: Modifier = Modifier
) {
Layout(
content = {
maxText()
minText()
// Set a fixed size to make the example easier to follow
BarChart(dataPoints, Modifier.size(200.dp))
},
modifier = modifier
) { measurables, constraints ->
check(measurables.size == 3)
val placeables = measurables.map {
it.measure(constraints.copy(minWidth = 0, minHeight = 0))
}
val maxTextPlaceable = placeables[0]
val minTextPlaceable = placeables[1]
val barChartPlaceable = placeables[2]
// Obtain the alignment lines from BarChart to position the Text
val minValueBaseline = barChartPlaceable[MinChartValue]
val maxValueBaseline = barChartPlaceable[MaxChartValue]
layout(constraints.maxWidth, constraints.maxHeight) {
maxTextPlaceable.placeRelative(
x = 0,
y = maxValueBaseline - (maxTextPlaceable.height / 2)
)
minTextPlaceable.placeRelative(
x = 0,
y = minValueBaseline - (minTextPlaceable.height / 2)
)
barChartPlaceable.placeRelative(
x = max(maxTextPlaceable.width, minTextPlaceable.width) + 20,
y = 0
)
}
}
}
@Preview
@Composable
fun ChartDataPreview() {
MaterialTheme {
BarChartMinMax(
dataPoints = listOf(4, 24, 15),
maxText = { Text("Max") },
minText = { Text("Min") },
modifier = Modifier.padding(24.dp)
)
}
}