Jetpack Compose 中的对齐线
在 Jetpack Compose 布局模型中,对齐线(AlignmentLine)允许创建自定义对齐参照,供父布局用来对齐和定位其子项。例如,Row 可以使用子项的自定义对齐线来对齐子项。
使用现有对齐线
某些 Compose 可组合项已自带对齐线。例如,BasicText 可组合项会公开 FirstBaseline 和 LastBaseline 对齐线。以下示例展示了如何创建一个自定义修饰符,向文本添加基于其第一条基线的内边距:
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
private fun TextWithPaddingToBaseline() {
MaterialTheme {
Text("Hi there!", Modifier.firstBaselineToTop(32.dp))
}
}
注意:Compose 库已经提供了 paddingFrom 修饰符,允许指定相对于任何对齐线的内边距。
创建自定义对齐线
创建自定义 Layout 可组合项或 LayoutModifier 时,可以提供自定义对齐线,供父级可组合项用来对齐和定位子项。
以下示例展示了一个自定义 BarChart 可组合项,它公开了两条对齐线:MaxChartValue 和 MinChartValue,使其他可组合项可以对齐到图表的最大和最小数据值。
首先,定义自定义对齐线:
/**
* 由[BarChart]中最大数据值定义的对齐线
*/
private val MaxChartValue = HorizontalAlignmentLine(merger = { old, new -> min(old, new) })
/**
* 由[BarChart]中最小数据值定义的对齐线
*/
private val MinChartValue = HorizontalAlignmentLine(merger = { old, new -> max(old, new) })
这些是 HorizontalAlignmentLine 类型,因为它们用于垂直对齐子项。合并策略考虑了 Compose 布局系统坐标(原点在左上角,正方向向下),因此最大值基线使用 min 策略,最小值基线使用 max 策略。
然后,在自定义布局中提供这些对齐线的值:
@Composable
private fun BarChart(
dataPoints: List<Int>,
modifier: Modifier = Modifier,
) {
val maxValue: Float = remember(dataPoints) { dataPoints.maxOrNull()!! * 1.2f }
BoxWithConstraints(modifier = modifier) {
val density = LocalDensity.current
with(density) {
// ...
// 计算基线
val maxYBaseline = // ...
val minYBaseline = // ...
Layout(
content = {},
modifier = Modifier.drawBehind {
// ...
}
) { _, constraints ->
with(constraints) {
layout(
width = if (hasBoundedWidth) maxWidth else minWidth,
height = if (hasBoundedHeight) maxHeight else minHeight,
// 在这里设置自定义对齐线,这些对齐线会传播到直接和间接父可组合项
alignmentLines = mapOf(
MinChartValue to minYBaseline.roundToInt(),
MaxChartValue to maxYBaseline.roundToInt()
)
) {}
}
}
}
}
}
使用自定义对齐线
父级布局可以使用子项提供的对齐线。以下示例创建了一个自定义布局,它将两个 Text 元素与 BarChart 的最大和最小数据值对齐:
@Composable
private fun BarChartMinMax(
dataPoints: List<Int>,
maxText: @Composable () -> Unit,
minText: @Composable () -> Unit,
modifier: Modifier = Modifier,
) {
Layout(
content = {
maxText()
minText()
// 设置固定大小以便于示例理解
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]
// 从 BarChart 获取对齐线以定位文本
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
private fun ChartDataPreview() {
MaterialTheme {
BarChartMinMax(
dataPoints = listOf(4, 24, 15),
maxText = { Text("Max") },
minText = { Text("Min") },
modifier = Modifier.padding(24.dp)
)
}
}