Garis perataan di Jetpack Compose

Model tata letak Compose memungkinkan Anda menggunakan AlignmentLine untuk membuat garis perataan kustom yang dapat digunakan oleh tata letak induk untuk meratakan dan memosisikan turunannya. Misalnya, Row dapat menggunakan garis perataan kustom turunannya untuk meratakannya.

Saat tata letak memberikan nilai untuk AlignmentLine tertentu, induk tata letak dapat membaca nilai ini setelah pengukuran, menggunakan operator Placeable.get pada instance Placeable yang sesuai. Berdasarkan posisi AlignmentLine, induk kemudian dapat menentukan posisi turunan.

Beberapa composable di Compose sudah dilengkapi dengan garis perataan. Misalnya, composable BasicText mengekspos garis perataan FirstBaseline dan LastBaseline.

Dalam contoh berikut, LayoutModifier kustom yang disebut firstBaselineToTop membaca FirstBaseline untuk menambahkan padding ke Text mulai dari dasar pengukuran pertamanya.

Menampilkan perbedaan antara menambahkan padding normal ke elemen,
dan menerapkan padding ke dasar pengukuran elemen Teks.
Gambar 1. Menampilkan perbedaan antara menambahkan padding normal ke elemen, dan menerapkan padding ke bagian dasar elemen Teks.

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))
    }
}

Untuk membaca FirstBaseline dalam contoh, placeable [FirstBaseline] digunakan dalam fase pengukuran.

Membuat garis perataan kustom

Saat membuat composable Layout kustom atau LayoutModifier kustom, Anda dapat memberikan garis perataan kustom sehingga composable induk lainnya dapat menggunakannya untuk meratakan dan memosisikan turunannya dengan sesuai.

Contoh berikut menunjukkan composable BarChart kustom yang mengekspos dua garis perataan, MaxChartValue, dan MinChartValue, sehingga composable lain dapat diratakan dengan nilai data minimum dan minimum pada diagram. Dua elemen teks, Max dan Min, telah diratakan ke tengah garis perataan kustom.

Composable BarChart dengan Teks yang disejajarkan dengan nilai data maksimum dan
minimum.
Gambar 2. Composable BarChart dengan Teks yang diselaraskan dengan nilai data maksimum dan minimum.

Garis perataan kustom ditentukan sebagai variabel level teratas dalam project Anda.

/**
 * 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)
})

Garis perataan khusus untuk membuat contoh adalah jenis HorizontalAlignmentLine, karena digunakan untuk meratakan turunan secara vertikal. Kebijakan penggabungan diteruskan sebagai parameter jika beberapa tata letak memberikan nilai untuk garis perataan ini. Karena koordinat sistem tata letak Compose dan koordinat Canvas mewakili [0, 0], pojok kiri atas dan sumbu x dan y positif ke bawah, sehingga nilai MaxChartValue akan selalu lebih kecil dari MinChartValue. Oleh karena itu, kebijakan penggabungan adalah min untuk nilai dasar pengukuran data diagram maksimum, dan max untuk nilai dasar pengukuran data diagram minimum.

Saat membuat Layout atau LayoutModifier kustom, tentukan garis perataan kustom dalam metode MeasureScope.layout, yang menggunakan parameter 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()
                        )
                    ) {}
                }
            }
        }
    }
}

Induk langsung dan tidak langsung dari composable ini dapat menggunakan garis perataan. Composable berikut akan membuat tata letak kustom yang menggunakan parameter, dua slot Text dan titik data, serta meratakan dua teks tersebut ke nilai data diagram maksimum dan minimum. Pratinjau composable ini adalah yang ditampilkan pada Gambar 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)
        )
    }
}