Benutzerdefinierte Layouts

In Compose werden UI-Elemente durch zusammensetzbare Funktionen dargestellt, die beim Aufrufen einen Teil der UI ausgeben. Dieser wird dann einem UI-Baum hinzugefügt, der auf dem Bildschirm gerendert wird. Jedes UI-Element hat ein übergeordnetes Element und möglicherweise viele untergeordnete Elemente. Jedes Element befindet sich auch innerhalb des übergeordneten Elements, das als Position (x, y) und als Größe als width und height angegeben wird.

Ü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 beschränken die minimalen und maximalen 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 meldet, kann es definieren, wie seine untergeordneten Elemente relativ zu sich platziert werden. Dies wird ausführlich unter Benutzerdefinierte Layouts erstellen beschrieben.

Das Layout jedes Knotens in der UI-Baumstruktur erfolgt in drei Schritten. Jeder Knoten muss:

  1. Alle Kinder erfassen
  2. Größe festlegen
  3. Untergeordnete Elemente platzieren

Die drei Schritte des Knotenlayouts: Untergeordnete messen, Größe festlegen, untergeordnete Elemente platzieren

Durch die Verwendung von Umfangsbereichen legen Sie fest, wann Sie Ihre untergeordneten Elemente messen und platzieren können. Die Messung eines Layouts kann nur während der Messung und der Layoutdurchläufe erfolgen. Ein untergeordnetes Element kann nur während der Layoutdurchläufe platziert werden und erst nach der Messung. Aufgrund von Erstellungsbereichen wie MeasureScope und PlacementScope wird dies bei der Kompilierung erzwungen.

Layoutmodifikator verwenden

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

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

Lassen Sie uns ein Text auf dem Bildschirm anzeigen und den Abstand von oben zur Referenz der ersten Textzeile festlegen. Genau das bewirkt der paddingFromBaseline-Modifikator. Wir implementieren ihn hier als Beispiel. Verwenden Sie dazu den layout-Modifikator, um die zusammensetzbare Funktion manuell auf dem Bildschirm zu platzieren. Hier sehen Sie das gewünschte Verhalten, bei dem der obere Abstand von Text auf 24.dp festgelegt ist:

Zeigt den Unterschied zwischen dem normalen Abstand zwischen den Elementen auf der Benutzeroberfläche, der den Abstand zwischen den Elementen festlegt, und dem Textabstand, der den Abstand von einer Baseline zur nächsten festlegt.

Hier ist der Code zum Erzeugen dieses Abstands:

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

In diesem Code geschieht Folgendes:

  1. Im Lambda-Parameter measurable messen Sie den Text, der durch den messbaren Parameter 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, die auch ein Lambda zum Platzieren der umschlossenen Elemente erzeugt. In diesem Fall ist das die Höhe zwischen der letzten Referenz und dem hinzugefügten oberen Abstand.
  3. Um die zusammengefassten Elemente auf dem Bildschirm zu positionieren, rufst du placeable.place(x, y) auf. Wenn die umschlossenen Elemente nicht platziert werden, sind sie nicht sichtbar. Die Position y entspricht dem oberen Abstand – der Position der ersten Referenz des Textes.

Um zu überprüfen, ob dies wie erwartet funktioniert, verwende diesen Modifikator auf Text:

@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 den Elementen an, die andere zeigt den Abstand von einer Baseline zur nächsten

Benutzerdefinierte Layouts erstellen

Der layout-Modifikator ändert nur die aufrufende zusammensetzbare Funktion. Wenn Sie mehrere zusammensetzbare Funktionen messen und für das Layout nutzen möchten, verwenden Sie stattdessen die zusammensetzbare Funktion Layout. Mit dieser zusammensetzbaren Funktion können Sie Kinder manuell messen und anordnen. Alle übergeordneten Layouts wie Column und Row werden mit der zusammensetzbaren Funktion Layout erstellt.

Erstellen wir eine sehr einfache Version von Column. 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 layout-Modifikator 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 Einschränkungen vom Typ Layout (ohne die Einschränkungen vom Typ minHeight) eingeschränkt und basierend auf den yPosition der vorherigen zusammensetzbaren Funktion platziert.

So würde diese benutzerdefinierte zusammensetzbare Funktion verwendet werden:

@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 in einer Spalte über dem nächsten.

Layoutrichtung

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

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

Wenn Sie layoutDirection verwenden, verwenden Sie place, um zusammensetzbare Funktionen zu platzieren. Im Gegensatz zur Methode placeRelative ändert sich place nicht je nach Layoutrichtung (von links nach rechts oder von rechts nach links).

Benutzerdefinierte Layouts in Aktion

Weitere Informationen zu Layouts und Modifikatoren finden Sie unter Grundlegende Layouts in Compose. Benutzerdefinierte Layouts finden Sie in den Beispielen zum Erstellen benutzerdefinierter Layouts.

Weitere Informationen

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

Videos