Ausrichtungslinien in Jetpack Compose

Im Layoutmodell „Compose“ können Sie mit AlignmentLine benutzerdefinierte Ausrichtungslinien erstellen, die von übergeordneten Layouts zum Ausrichten und Positionieren der untergeordneten Elemente verwendet werden können. Row kann beispielsweise die benutzerdefinierten Ausrichtungslinien der untergeordneten Elemente verwenden, um sie auszurichten.

Wenn ein Layout einen Wert für eine bestimmte AlignmentLine bereitstellt, können die übergeordneten Elemente des Layouts diesen Wert nach dem Messen lesen, indem sie den Operator Placeable.get auf der entsprechenden Instanz Placeable verwenden. Anhand der Position von AlignmentLine können die übergeordneten Elemente dann die Positionierung der untergeordneten Elemente festlegen.

Einige zusammensetzbare Funktionen in „Schreiben“ sind bereits mit Ausrichtungslinien ausgestattet. Beispielsweise werden in der zusammensetzbaren Funktion BasicText die Ausrichtungslinien FirstBaseline und LastBaseline angezeigt.

Im folgenden Beispiel liest ein benutzerdefiniertes LayoutModifier namens firstBaselineToTop den FirstBaseline, um dem Text beginnend mit der ersten Referenz einen Innenrand hinzuzufügen.

Abbildung 1: Zeigt den Unterschied zwischen dem Hinzufügen eines normalen Innenrands für ein Element und dem Anwenden eines Innenrands auf die Referenz eines Textelements.

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

Zum Lesen der FirstBaseline im Beispiel wird placeable [FirstBaseline] in der Messphase verwendet.

Benutzerdefinierte Ausrichtungslinien erstellen

Wenn Sie eine benutzerdefinierte Layout- oder eine benutzerdefinierte LayoutModifier erstellen, können Sie benutzerdefinierte Ausrichtungslinien angeben, damit andere übergeordnete zusammensetzbare Funktionen sie verwenden können, um ihre untergeordneten Elemente entsprechend auszurichten und zu positionieren.

Das folgende Beispiel zeigt eine benutzerdefinierte zusammensetzbare Funktion BarChart, die zwei Ausrichtungslinien, MaxChartValue und MinChartValue, enthält, damit andere zusammensetzbare Funktionen am maximalen und minimalen Datenwert des Diagramms ausgerichtet werden können. Die beiden Textelemente Max und Min wurden an der Mitte der benutzerdefinierten Ausrichtungslinien ausgerichtet.

Abbildung 2: BarChart kann mit Text zusammensetzbar sein, der auf den Höchst- und Mindestwert ausgerichtet ist.

Benutzerdefinierte Ausrichtungslinien werden als Variablen der obersten Ebene in Ihrem Projekt definiert.

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

Die benutzerdefinierten Ausrichtungslinien, mit denen wir unser Beispiel erstellen, sind vom Typ HorizontalAlignmentLine, da sie verwendet werden, um untergeordnete Elemente vertikal auszurichten. Für den Fall, dass mehrere Layouts einen Wert für diese Ausrichtungslinien bereitstellen, wird eine Zusammenführungsrichtlinie als Parameter übergeben. Da die Koordinaten des Layoutsystems und die Canvas-Koordinaten [0, 0] darstellen, sind die obere linke Ecke sowie die Achse x und y nach unten positiv, sodass der Wert MaxChartValue immer kleiner als MinChartValue ist. Daher lautet die Fusionsrichtlinie min für die maximale Referenz des Diagrammdatenwerts und max für die minimale Referenzwert der Diagrammdaten.

Geben Sie beim Erstellen eines benutzerdefinierten Layout- oder LayoutModifier-Elements in der Methode MeasureScope.layout benutzerdefinierte Ausrichtungslinien an, für die ein alignmentLines: Map<AlignmentLine, Int>-Parameter verwendet wird.

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

Direkte und indirekte übergeordnete und indirekte übergeordnete dieser zusammensetzbaren Funktion können die Ausrichtungslinien nutzen. Die folgende zusammensetzbare Funktion erstellt ein benutzerdefiniertes Layout, das zwei Text-Slots und Datenpunkte als Parameter verwendet und die beiden Texte an den maximalen und minimalen Diagrammdatenwerten ausrichtet. Eine Vorschau dieser zusammensetzbaren Funktion ist in Abbildung 2 zu sehen.

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