Ausrichtungslinien in Jetpack Compose

Im Layoutmodell „Compose“ können Sie mit AlignmentLine benutzerdefinierte Ausrichtungslinien erstellen, die von übergeordneten Layouts zum Ausrichten und Positionieren von untergeordneten Layouts verwendet werden können. Beispielsweise kann Row die benutzerdefinierten Ausrichtungslinien seiner 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 der Messung mithilfe des Operators Placeable.get in der entsprechenden Placeable-Instanz lesen. Anhand der Position von AlignmentLine können die Eltern dann die Position der untergeordneten Elemente festlegen.

Einige zusammensetzbare Funktionen in der Funktion „Compose“ enthalten bereits Ausrichtungslinien. Die zusammensetzbare Funktion BasicText macht beispielsweise die Ausrichtungslinien FirstBaseline und LastBaseline verfügbar.

Im folgenden Beispiel liest ein benutzerdefiniertes LayoutModifier namens firstBaselineToTop FirstBaseline, um dem Text ausgehend von seiner ersten Referenz einen Innenabstand hinzuzufügen.

Abbildung 1: Zeigt den Unterschied zwischen dem Hinzufügen eines normalen Abstands zu einem Element und dem Anwenden eines Abstands 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 von FirstBaseline im Beispiel wird placeable [FirstBaseline] in der Messphase verwendet.

Benutzerdefinierte Ausrichtungslinien erstellen

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

Das folgende Beispiel zeigt eine benutzerdefinierte zusammensetzbare Funktion BarChart mit zwei Ausrichtungslinien, MaxChartValue und MinChartValue, damit andere zusammensetzbare Funktionen an den 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 zusammensetzbar mit Text, der auf den maximalen und minimalen Datenwert ausgerichtet ist.

Benutzerdefinierte Ausrichtungslinien werden in Ihrem Projekt als Variablen der obersten Ebene 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 in unserem Beispiel sind vom Typ HorizontalAlignmentLine, da damit untergeordnete Elemente vertikal ausgerichtet werden. Eine Zusammenführungsrichtlinie wird als Parameter übergeben, wenn mehrere Layouts einen Wert für diese Ausrichtungslinien bereitstellen. Da die Koordinaten des Layoutsystems von Compose und die Canvas-Koordinaten [0, 0] darstellen, sind die obere linke Ecke und die Achsen x und y nach unten positiv, sodass der Wert MaxChartValue immer kleiner als MinChartValue ist. Daher lautet die Zusammenführungsrichtlinie min für die maximale Referenz des Diagrammdatenwerts und max für die minimale Referenz des Diagrammdatenwerts.

Geben Sie beim Erstellen eines benutzerdefinierten Layout oder LayoutModifier benutzerdefinierte Ausrichtungslinien in der Methode MeasureScope.layout 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 Elemente dieser zusammensetzbaren Funktion können die Ausrichtungslinien nutzen. Mit der folgenden zusammensetzbaren Funktion wird ein benutzerdefiniertes Layout erstellt, das als Parameter zwei Text-Slots und Datenpunkte verwendet und die beiden Texte auf die maximalen und minimalen Diagrammdatenwerte ausrichtet. Die 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)
        )
    }
}