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 melakukan pengukuran, menggunakan operator Placeable.get pada instance Placeable yang terkait. Berdasarkan posisi AlignmentLine, induk kemudian dapat memutuskan posisi turunan.

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

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

Gambar 1. Menampilkan perbedaan antara menambahkan padding normal ke elemen, dan menerapkan padding ke dasar pengukuran 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 ini, placeable [FirstBaseline] digunakan dalam fase pengukuran.

Membuat garis perataan khusus

Saat membuat composable Layout kustom atau LayoutModifier kustom, Anda dapat memberikan garis perataan khusus sehingga composable induk lain dapat menggunakannya untuk meratakan dan memosisikan turunannya sebagaimana mestinya.

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, Maks dan Min, telah diratakan ke tengah garis perataan khusus.

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

Garis perataan khusus ditetapkan sebagai variabel level atas 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 penggabungannya adalah min untuk dasar pengukuran nilai data diagram maksimum, dan max untuk dasar pengukuran nilai data diagram minimum.

Saat membuat Layout atau LayoutModifier kustom, tentukan garis perataan khusus 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)
        )
    }
}