Benutzerdefinierte Layouts

In Compose werden UI-Elemente durch die zusammensetzbaren Funktionen dargestellt, die bei Aufruf ein UI-Element ausgeben, das dann einem UI-Baum hinzugefügt wird, der auf dem Bildschirm gerendert wird. Jedes UI-Element hat ein übergeordnetes Element und potenziell viele untergeordnete Elemente. Jedes Element befindet sich auch innerhalb seines übergeordneten Elements, angegeben als (x, y)-Position, und hat eine Größe, angegeben als width und height.

Übergeordnete Elemente definieren die Einschränkungen für ihre untergeordneten Elemente. Ein Element wird aufgefordert, seine Größe innerhalb dieser Einschränkungen zu definieren. Einschränkungen begrenzen die minimale und maximale width und height eines Elements. Wenn ein Element untergeordnete Elemente hat, kann es jedes untergeordnete Element messen, um seine Größe zu bestimmen. Sobald ein Element seine eigene Größe bestimmt und gemeldet hat, kann es definieren, wie seine untergeordneten Elemente relativ zu ihm platziert werden sollen. Weitere Informationen finden Sie unter Benutzerdefinierte Layouts erstellen.

Das Layout jedes Knotens im UI-Baum erfolgt in drei Schritten. Jeder Knoten muss:

  1. Alle untergeordneten Elemente messen
  2. Seine eigene Größe bestimmen
  3. Seine untergeordneten Elemente platzieren
Drei Schritte für das Knotenlayout: Kinder messen, Größe festlegen, Kinder platzieren
Abbildung 1. Die drei Schritte des Knotenlayouts sind das Messen untergeordneter Elemente, das Bestimmen der Größe, und das Platzieren untergeordneter Elemente.

Die Verwendung von Bereichen definiert, wann Sie Ihre untergeordneten Elemente messen und platzieren können. Das Messen eines Layouts ist nur während der Mess- und Layoutdurchläufe möglich. Ein untergeordnetes Element kann nur während der Layoutdurchläufe platziert werden (und erst, nachdem es gemessen wurde). Aufgrund von Compose-Bereichen wie MeasureScope, und PlacementScope, wird dies zur Kompilierzeit erzwungen.

Layout-Modifizierer verwenden

Mit dem Modifizierer layout können Sie ändern, wie ein Element gemessen und angeordnet wird. Layout ist eine Lambda-Funktion. Zu den Parametern gehören das Element, das Sie messen können, übergeben als measurable, und die eingehenden Einschränkungen dieser zusammensetzbaren Funktion, übergeben als constraints. Ein benutzerdefinierter Layout-Modifizierer kann so aussehen:

fun Modifier.customLayoutModifier() =
    layout { measurable, constraints ->
        // ...
    }

Zeigen Sie einen Text auf dem Bildschirm an und steuern Sie den Abstand von oben zur Grundlinie der ersten Textzeile. Genau das macht der Modifizierer paddingFromBaseline. Sie implementieren ihn hier als Beispiel. Verwenden Sie dazu den Modifizierer layout, um die zusammensetzbare Funktion manuell auf dem Bildschirm zu platzieren. Hier ist das resultierende Verhalten, bei dem der obere Abstand von Text auf 24.dp festgelegt ist:

Zeigt den Unterschied zwischen normalem UI-Abstand, der den Abstand zwischen Elementen festlegt, und Textabstand, der den Abstand von einer Grundlinie zur nächsten festlegt
Abbildung 2. Text mit angewendetem paddingFromBaseline.

Hier ist der Code, um diesen Abstand zu erzeugen:

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

Das passiert in diesem Code:

  1. Im Lambda-Parameter measurable messen Sie den Text, der durch den Parameter „measurable“ dargestellt wird, indem Sie measurable.measure(constraints) aufrufen.
  2. Sie geben die Größe der zusammensetzbaren Funktion an, indem Sie die Methode layout(width, height) aufrufen. Diese Methode gibt auch eine Lambda-Funktion zurück, die zum Platzieren der umschlossenen Elemente verwendet wird. In diesem Fall ist es die Höhe zwischen der letzten Grundlinie und dem hinzugefügten oberen Abstand.
  3. Sie positionieren die umschlossenen Elemente auf dem Bildschirm, indem Sie placeable.place(x, y) aufrufen. Wenn die umschlossenen Elemente nicht platziert werden, sind sie nicht sichtbar. Die y-Position entspricht dem oberen Abstand: der Position der ersten Grundlinie des Texts.

Verwenden Sie diesen Modifizierer für einen Text, um zu prüfen, ob er wie erwartet funktioniert:

@Preview
@Composable
fun TextWithPaddingToBaselinePreview() {
    MyApplicationTheme {
        Text("Hi there!", Modifier.firstBaselineToTop(32.dp))
    }
}

@Preview
@Composable
fun TextWithNormalPaddingPreview() {
    MyApplicationTheme {
        Text("Hi there!", Modifier.padding(top = 32.dp))
    }
}

Mehrere Vorschauen von Textelementen. Eine zeigt den normalen Abstand zwischen Elementen, die andere den Abstand von einer Grundlinie zur nächsten.
Abbildung 3. Modifizierer auf eine zusammensetzbare Funktion vom Typ Text angewendet und in der Vorschau angezeigt.

Benutzerdefinierte Layouts erstellen

Der Modifizierer layout ändert nur die aufrufende zusammensetzbare Funktion. Wenn Sie mehrere zusammensetzbare Funktionen messen und anordnen möchten, verwenden Sie stattdessen die Layout zusammensetzbare Funktion. Mit dieser zusammensetzbaren Funktion können Sie untergeordnete Elemente manuell messen und anordnen. Alle Layouts höherer Ebene wie Column und Row werden mit der zusammensetzbaren Funktion Layout erstellt.

In diesem Beispiel wird eine sehr einfache Version von Column erstellt. Die meisten benutzerdefinierten Layouts folgen diesem Muster:

@Composable
fun MyBasicColumn(
    modifier: Modifier = Modifier,
    content: @Composable () -> Unit
) {
    Layout(
        modifier = modifier,
        content = content
    ) { measurables, constraints ->
        // measure and position children given constraints logic here
        // ...
    }
}

Ähnlich wie beim Modifizierer layout ist measurables die Liste der untergeordneten Elemente, die gemessen werden müssen, und constraints sind die Einschränkungen des übergeordneten Elements. Nach derselben Logik wie zuvor kann MyBasicColumn so implementiert werden:

@Composable
fun MyBasicColumn(
    modifier: Modifier = Modifier,
    content: @Composable () -> Unit
) {
    Layout(
        modifier = modifier,
        content = content
    ) { measurables, constraints ->
        // Don't constrain child views further, measure them with given constraints
        // List of measured children
        val placeables = measurables.map { measurable ->
            // Measure each children
            measurable.measure(constraints)
        }

        // Set the size of the layout as big as it can
        layout(constraints.maxWidth, constraints.maxHeight) {
            // Track the y co-ord we have placed children up to
            var yPosition = 0

            // Place children in the parent layout
            placeables.forEach { placeable ->
                // Position item on the screen
                placeable.placeRelative(x = 0, y = yPosition)

                // Record the y co-ord placed up to
                yPosition += placeable.height
            }
        }
    }
}

Die untergeordneten zusammensetzbaren Funktionen werden durch die Layout-Einschränkungen (ohne die minHeight-Einschränkungen) eingeschränkt und basierend auf der yPosition der vorherigen zusammensetzbaren Funktion platziert.

So wird die benutzerdefinierte zusammensetzbare Funktion verwendet:

@Composable
fun CallingComposable(modifier: Modifier = Modifier) {
    MyBasicColumn(modifier.padding(8.dp)) {
        Text("MyBasicColumn")
        Text("places items")
        Text("vertically.")
        Text("We've done it by hand!")
    }
}

Mehrere Textelemente, die in einer Spalte übereinander gestapelt sind.
Abbildung 4. Benutzerdefinierte Column-Implementierung.

Layoutrichtung

Ändern Sie die Layoutrichtung einer zusammensetzbaren Funktion, indem Sie die LocalLayoutDirection lokale Zusammensetzung ändern.

Wenn Sie zusammensetzbare Funktionen manuell auf dem Bildschirm platzieren, ist die LayoutDirection Teil des LayoutScope des Modifizierers layout oder der zusammensetzbaren Funktion Layout.

Wenn Sie layoutDirection verwenden, platzieren Sie zusammensetzbare Funktionen mit place. Anders als bei der placeRelative Methode ändert sich place nicht basierend auf der Layoutrichtung (von links nach rechts oder von rechts nach links).

Benutzerdefinierte Layouts in Aktion

Weitere Informationen zu Layouts und Modifizierern finden Sie unter Einfache Layouts in Compose, Benutzerdefinierte Layouts in Aktion sehen Sie in den Compose-Beispielen, in denen benutzerdefinierte Layouts erstellt werden.

Weitere Informationen

Weitere Informationen zu benutzerdefinierten Layouts in Compose finden Sie in den folgenden zusätzlichen Ressourcen.

Videos