Jetpack Compose'da hizalama çizgileri

Oluşturma düzeni modeli, AlignmentLine kullanarak alt öğelerini hizalamak ve konumlandırmak için ebeveyn düzenler tarafından kullanılabilecek özel hizalama çizgileri oluşturmanıza olanak tanır. Örneğin, Row, alt öğelerini hizalamak için alt öğelerin özel hizalama çizgilerini kullanabilir.

Bir düzen belirli bir AlignmentLine için bir değer sağladığında, düzenin üst öğeleri, ilgili Placeable örneğinde Placeable.get operatörünü kullanarak ölçümden sonra bu değeri okuyabilir. Ebeveynler, AlignmentLine'ün konumuna göre çocukların konumuna karar verebilir.

Oluşturma bölümündeki bazı bileşenler zaten hizalama çizgileriyle birlikte gelir. Örneğin, BasicText birleşik öğesi FirstBaseline ve LastBaseline hizalama çizgilerini gösterir.

Aşağıdaki örnekte, firstBaselineToTop adlı özel bir LayoutModifier, ilk taban çizgisinden itibaren Text'e dolgu eklemek için FirstBaseline'i okur.

Şekil 1. Bir öğeye normal dolgu ekleme ile metin öğesinin taban çizgisine dolgu uygulama arasındaki farkı gösterir.

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

Örnekte FirstBaseline değerini okumak için ölçüm aşamasında placeable [FirstBaseline] kullanılır.

Özel hizalama çizgileri oluşturma

Özel bir Layout bileşeni veya özel bir LayoutModifier oluştururken, diğer üst bileşenlerin alt bileşenlerini buna göre hizalamak ve konumlandırmak için kullanabileceği özel hizalama çizgileri sağlayabilirsiniz.

Aşağıdaki örnekte, diğer bileşenlerin grafiğin maksimum ve minimum veri değerine hizalanabilmesi için iki hizalama çizgisi (MaxChartValue ve MinChartValue) gösteren özel bir BarChart bileşeni gösterilmektedir. Maks ve Min adlı iki metin öğesi, özel hizalama çizgilerinin ortasına hizalanmıştır.

Şekil 2. BarChart, maksimum ve minimum veri değerine hizalanmış metinle birleştirilebilir.

Özel hizalama çizgileri, projenizde üst düzey değişkenler olarak tanımlanır.

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

Örneğimizi oluşturmak için kullandığımız özel hizalama çizgileri, alt öğeleri dikey olarak hizalamak için kullanıldığı için HorizontalAlignmentLine türündedir. Birden fazla düzenin bu hizalama çizgileri için bir değer sağlaması ihtimaline karşı bir birleştirme politikası parametre olarak iletilir. Oluşturma düzeni sistem koordinatları ve Canvas koordinatları [0, 0]'ı temsil ettiğinden, sol üst köşe ve x ile y ekseni aşağı doğru pozitif olduğundan MaxChartValue değeri her zaman MinChartValue'ten daha küçük olacaktır. Bu nedenle, birleştirme politikası maksimum grafik veri değeri referans değeri için min, minimum grafik veri değeri referans değeri için max'tür.

Özel bir Layout veya LayoutModifier oluştururken alignmentLines: Map<AlignmentLine, Int> parametresi alan MeasureScope.layout yönteminde özel hizalama satırları belirtin.

@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()
                        )
                    ) {}
                }
            }
        }
    }
}

Bu bileşimin doğrudan ve dolaylı ebeveynleri, hizalama satırlarını kullanabilir. Aşağıdaki birleştirilebilir öğe, parametre olarak iki Text slotu ve veri noktasını alan ve iki metni maksimum ve minimum grafik veri değerlerine hizalayan özel bir düzen oluşturur. Bu bileşiğin önizlemesi Şekil 2'de gösterilmektedir.

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