Jetpack Compose 中的對齊線

透過集合功能整理內容 你可以依據偏好儲存及分類內容。

Compose 版面配置模型可讓您使用 AlignmentLine 建立自訂的對齊線,上層布局可使用此線對齊和定位其子項。舉例來說, Row 可以使用其子項的自訂對齊線來對齊。

如果版面配置提供特定 AlignmentLine 的值,版面配置的父項可以使用對應 Placeable 執行個體的 Placeable.get 運算子,在測量後讀取此值。根據 AlignmentLine 的位置,父項接下來可以決定子項的位置。

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

在下面的範例中,自訂 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
fun TextWithPaddingToBaselinePreview() {
    MaterialTheme {
        Text("Hi there!", Modifier.firstBaselineToTop(32.dp))
    }
}

為讀取範例中的 FirstBaseline,測量階段會使用 placeable [FirstBaseline]

建立自訂對齊線

建立自訂 Layout 可組合項或自訂 LayoutModifier 時,您可以提供自訂對齊線,讓其他父項可組合元件可以使用該線,並且據此決定子項的位置。

以下範例顯示自訂 BarChart 可組合項,其中顯示 MaxChartValueMinChartValue 兩個對齊線,因此其他可組合元素能符合圖表的最大和最小資料值。兩個文字元素 (MaxMin) 已經在自訂對齊線的中心對齊。

圖 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] 時,左上角以及 xy 軸會是 正向朝下,因此 MaxChartValue 值將始終小於 MinChartValue。因此,最大圖表資料值基準的合併政策為 min,最小圖表資料值基準的合併政策為 max

建立自訂 LayoutLayoutModifier 時,請在使用 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)
        )
    }
}