Jetpack Compose 中的對齊線

Compose 版面配置模型可讓您使用 AlignmentLine 建立自訂 上層布係版面配置可使用此對齊線對齊和定位 。例如: Row敬上 可以使用子項的自訂對齊線來對齊。

如果版面配置提供特定 AlignmentLine 的值,該版面配置的 家長可以使用 Placeable.get,在測量後讀取此值 相同的運算子 Placeable 執行個體。 根據 AlignmentLine 的位置,家長可以 然後決定子項的位置

Compose 中的部分可組合項已帶有對齊線。舉例來說, BasicText敬上 可組合函式會顯示 FirstBaselineLastBaseline 對齊線。

在以下範例中,名為「LayoutModifier」的自訂 LayoutModifier firstBaselineToTop 會讀取 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
private fun TextWithPaddingToBaseline() {
    MaterialTheme {
        Text("Hi there!", Modifier.firstBaselineToTop(32.dp))
    }
}

如要讀取範例中的 FirstBaseline, 評估階段會使用 placeable [FirstBaseline]

建立自訂對齊線

建立自訂 Layout 時 自訂 LayoutModifier,您只需提供 自訂對齊線,讓其他父項可組合函式可以使用該線對齊 並據此定位子項

以下範例顯示自訂 BarChart 可組合函式,在當中顯示兩個 對齊線、MaxChartValueMinChartValue,以便讓其他可組合函式 可用於對齊圖表的最大值和最小值。兩段文字 MaxMin 元素已經與自訂的 對齊線。

圖 2. BarChart 可組合函式的文字符合最大值和 資料值。

自訂對齊線行在專案中定義為頂層變數。

/**
 * AlignmentLine defined by the maximum data value in a [BarChart]
 */
private val MaxChartValue = HorizontalAlignmentLine(merger = { old, new ->
    min(old, new)
})

/**
 * AlignmentLine defined by the minimum data value in a [BarChart]
 */
private val MinChartValue = HorizontalAlignmentLine(merger = { old, new ->
    max(old, new)
})

用來建立範例的自訂對齊線是 HorizontalAlignmentLine, 這些元件是用來垂直對齊子項系統會以 參數,藉此為這些對齊線提供一個值。阿斯 Compose 版面配置系統座標和 Canvas 座標代表 [0, 0],左上角以及 x 軸和 y 軸 正朝下,因此 MaxChartValue 值將一律小於 MinChartValue。因此,最大圖表的合併政策為 min 資料值基準,以及 max 代表最小圖表資料值基準。

建立自訂 LayoutLayoutModifier 時,指定自訂對齊方式 MeasureScope.layout 中的線條 方法,此方法會採用 alignmentLines: Map<AlignmentLine, Int> 參數。

@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) {
            // ...
            // Calculate baselines
            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,
                        // Custom AlignmentLines are set here. These are propagated
                        // to direct and indirect parent composables.
                        alignmentLines = mapOf(
                            MinChartValue to minYBaseline.roundToInt(),
                            MaxChartValue to maxYBaseline.roundToInt()
                        )
                    ) {}
                }
            }
        }
    }
}

這個可組合函式的直接和間接父項可以使用對齊方式 線條。下列可組合函式可建立自訂版面配置,將 參數 2 Text 位置和資料點,並將這兩個文字與 圖表資料的最大值和最小值。這個可組合函式的預覽畫面為 如圖 2 所示

@Composable
private 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
private fun ChartDataPreview() {
    MaterialTheme {
        BarChartMinMax(
            dataPoints = listOf(4, 24, 15),
            maxText = { Text("Max") },
            minText = { Text("Min") },
            modifier = Modifier.padding(24.dp)
        )
    }
}