Linie wyrównania w Jetpack Compose

Model układu Utwórz umożliwia tworzenie niestandardowych linii wyrównania za pomocą narzędzia AlignmentLine, którego mogą używać układy nadrzędne do wyrównywania i pozycjonowania elementów podrzędnych. Na przykład Row może wyrównać elementy podrzędne za pomocą niestandardowych linii wyrównywania.

Gdy układ podaje wartość określonej AlignmentLine, elementy nadrzędne układu mogą odczytać tę wartość po przeprowadzeniu pomiaru, używając operatora Placeable.get w odpowiednim instancji Placeable. Na podstawie pozycji AlignmentLine rodzice mogą zdecydować o położeniu elementów podrzędnych.

Niektóre elementy kompozycyjne w sekcji Utwórz mają już linie wyrównania. Na przykład funkcja kompozycyjna BasicText ujawnia linie wyrównania FirstBaseline i LastBaseline.

W przykładzie poniżej niestandardowy obiekt LayoutModifier o nazwie firstBaselineToTop odczytuje wartość FirstBaseline, aby dodać dopełnienie do elementu Text, począwszy od pierwszej wartości bazowej.

Rysunek 1. Pokazuje różnicę między dodaniem normalnego dopełnienia elementu a dodaniem dopełnienia do punktu odniesienia elementu tekstowego.

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

Do odczytywania parametru FirstBaseline w przykładzie używany jest placeable [FirstBaseline] na etapie pomiaru.

Utwórz niestandardowe linie wyrównania

Podczas tworzenia niestandardowego elementu Layout kompozycyjnego lub niestandardowego elementu LayoutModifier możesz podać niestandardowe linie wyrównania, aby inne nadrzędne elementy kompozycyjne mogły używać ich do wyrównywania i pozycjonowania elementów podrzędnych.

Poniższy przykład przedstawia niestandardowy element kompozycyjny BarChart z 2 liniami wyrównania: MaxChartValue i MinChartValue, dzięki czemu inne funkcje kompozycyjne mogą być zgodne z maksymalną i minimalną wartością danych na wykresie. Dwa elementy tekstowe – Max i Min – zostały wyrównane do środka niestandardowych linii wyrównania.

Rysunek 2. Element kompozycyjny BarChart z tekstem wyrównanym do maksymalnej i minimalnej wartości danych.

Niestandardowe linie wyrównania są zdefiniowane jako zmienne najwyższego poziomu w projekcie.

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

Niestandardowe linie wyrównania używane w naszym przykładzie mają typ HorizontalAlignmentLine, ponieważ służą do wyrównywania elementów podrzędnych w pionie. Zasada scalania jest przekazywana jako parametr w przypadku, gdy wiele układów podaje wartość dla tych linii wyrównania. Jako że współrzędne systemu układu tworzenia tworzenia, a współrzędne Canvas to [0, 0], lewy górny róg i oś x i y mają wartości dodatnie, więc wartość MaxChartValue będzie zawsze mniejsza niż MinChartValue. Dlatego zasada scalania wynosi min w przypadku maksymalnej wartości bazowej danych wykresu i max dla minimalnej wartości bazowej wykresu.

Podczas tworzenia niestandardowego elementu Layout lub LayoutModifier określ niestandardowe linie wyrównania w metodzie MeasureScope.layout, która pobiera parametr 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()
                        )
                    ) {}
                }
            }
        }
    }
}

Bezpośrednie i pośrednie elementy nadrzędne tego elementu kompozycyjnego mogą wykorzystywać linie wyrównania. Podany niżej element kompozycyjny tworzy układ niestandardowy, który jako parametr przyjmuje jako parametr 2 boksy Text i punkty danych, a potem wyrównuje te 2 teksty do minimalnych i maksymalnych wartości danych na wykresie. Podgląd tego elementu kompozycyjnego jest widoczny na Rysunku 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)
        )
    }
}