Benutzerdefinierte Layouts

In Compose werden UI-Elemente durch zusammensetzbare Funktionen dargestellt, die beim Aufruf 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 potenziell viele untergeordnete Elemente. Jedes Element befindet sich außerdem 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. Nachdem ein Element seine eigene Größe bestimmt und gemeldet hat, kann es definieren, wie seine untergeordneten Elemente relativ zu ihm platziert werden sollen. Dies wird im Abschnitt Benutzerdefinierte Layouts erstellen ausführlich beschrieben.

Das Layout der einzelnen Knoten im UI-Baum ist ein dreistufiger Prozess. Jeder Knoten muss:

  1. Kinder messen
  2. Größe selbst festlegen
  3. Untergeordnete Elemente platzieren
Drei Schritte des Knotenlayouts: Untergeordnete Elemente messen, Größe festlegen, untergeordnete Elemente platzieren
Abbildung 1. Die drei Schritte des Knotenlayouts sind: Untergeordnete Elemente messen, Größe festlegen und untergeordnete Elemente platzieren.

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

Layoutmodifikator verwenden

Mit dem Modifikator layout können Sie ändern, wie ein Element gemessen und angeordnet wird. Layout ist ein Lambda-Ausdruck. 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 Layoutmodifikator kann so aussehen:

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

Zeigt ein Text auf dem Bildschirm an und steuert den Abstand vom oberen Rand zur Grundlinie der ersten Textzeile. Genau das macht der Modifikator paddingFromBaseline. Sie implementieren ihn hier als Beispiel. Verwende dazu den Modifikator layout, um die komponierbare Funktion manuell auf dem Bildschirm zu platzieren. Hier ist das resultierende Verhalten, wenn das obere Padding von Text auf 24.dp gesetzt ist:

Zeigt den Unterschied zwischen normalem UI-Padding, das den Abstand zwischen Elementen festlegt, und Text-Padding, das 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 messbaren Parameter dargestellt wird, indem Sie measurable.measure(constraints) aufrufen.
  2. Sie geben die Größe der komponierbaren Funktion an, indem Sie die Methode layout(width, height) aufrufen. Dadurch wird auch ein Lambda-Ausdruck bereitgestellt, mit dem die umschlossenen Elemente platziert werden. 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 Position y entspricht dem oberen Abstand: der Position der ersten Grundlinie des Texts.

Wenn Sie prüfen möchten, ob dies wie erwartet funktioniert, verwenden Sie diesen Modifikator für ein 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 Elementen, die andere den Abstand von einer Grundlinie zur nächsten.
Abbildung 3. Auf ein Text-Composable angewendeter und in der Vorschau angezeigter Modifikator.

Benutzerdefinierte Layouts erstellen

Der Modifikator layout ändert nur die aufrufende Komposition. Wenn Sie mehrere Composables messen und anordnen möchten, verwenden Sie stattdessen das Composable Layout. Mit dieser zusammensetzbaren Funktion können Sie untergeordnete Elemente manuell messen und anordnen. Alle übergeordneten Layouts 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 Modifikator 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 Composables sind durch die Layout-Einschränkungen (ohne die minHeight-Einschränkungen) eingeschränkt und werden basierend auf dem yPosition des vorherigen Composables platziert.

So wird diese benutzerdefinierte Komposition 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 angeordnet sind.
Abbildung 4. Benutzerdefinierte Column-Implementierung.

Layoutrichtung

Sie können die Layoutrichtung einer zusammensetzbaren Funktion ändern, indem Sie die lokale Zusammensetzung LocalLayoutDirection ändern.

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

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

Benutzerdefinierte Layouts in der Praxis

Weitere Informationen zu Layouts und Modifikatoren findest du unter Grundlegende Layouts in Compose. Benutzerdefinierte Layouts in Aktion findest du in den Compose-Beispielen, die benutzerdefinierte Layouts erstellen.

Weitere Informationen

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

Videos