Grundlagen zum Verfassen von Layouts

Mit Jetpack Compose können Sie die Benutzeroberfläche Ihrer App viel einfacher entwerfen und erstellen. Composewandelt den Status über folgende Methoden in UI-Elemente um:

  1. Zusammensetzung der Elemente
  2. Layout von Elementen
  3. Zeichnen von Elementen

Transformierenden Status über Komposition, Layout und Zeichnung in die Benutzeroberfläche einbinden

In diesem Dokument geht es hauptsächlich um das Layout von Elementen. Außerdem werden einige der Bausteine von Compose erläutert, mit denen Sie Ihre UI-Elemente layouten können.

Ziele von Layouts in Compose

Die Jetpack Compose-Implementierung des Layoutsystems hat zwei Hauptziele:

Grundlagen komponierbarer Funktionen

Zusammensetzbare Funktionen sind der Grundbaustein von Compose. Eine zusammensetzbare Funktion ist eine Funktion, die Unit ausgibt und einen Teil Ihrer UI beschreibt. Die Funktion nimmt eine Eingabe entgegen und generiert die Inhalte, die auf dem Bildschirm angezeigt werden. Weitere Informationen zu Composeable-Modellen finden Sie in der Dokumentation Ein mentales Modell erstellen.

Eine kombinierbare Funktion kann mehrere UI-Elemente ausgeben. Wenn Sie jedoch keine Vorgaben dazu machen, wie sie angeordnet werden sollen, ordnet Compose die Elemente möglicherweise so an, wie Sie es nicht möchten. Mit diesem Code werden beispielsweise zwei Textelemente generiert:

@Composable
fun ArtistCard() {
    Text("Alfred Sisley")
    Text("3 minutes ago")
}

Ohne Angabe der gewünschten Anordnung stapelt Compose die Textelemente übereinander, wodurch sie unlesbar werden:

Zwei Textelemente, die übereinander liegen und den Text unleserlich machen

Compose bietet eine Sammlung von vorgefertigten Layouts, mit denen Sie Ihre UI-Elemente anordnen können. Außerdem können Sie damit ganz einfach eigene, speziellere Layouts definieren.

Komponenten des Standardlayouts

In vielen Fällen können Sie einfach die Standardlayoutelemente von Compose verwenden.

Mit Column können Sie Elemente vertikal auf dem Bildschirm platzieren.

@Composable
fun ArtistCardColumn() {
    Column {
        Text("Alfred Sisley")
        Text("3 minutes ago")
    }
}

Zwei Textelemente in einem Spaltenlayout angeordnet, damit der Text gut lesbar ist

Mit Row können Sie Elemente auch horizontal auf dem Bildschirm platzieren. Sowohl Column als auch Row unterstützen die Konfiguration der Ausrichtung der enthaltenen Elemente.

@Composable
fun ArtistCardRow(artist: Artist) {
    Row(verticalAlignment = Alignment.CenterVertically) {
        Image(bitmap = artist.image, contentDescription = "Artist image")
        Column {
            Text(artist.name)
            Text(artist.lastSeenOnline)
        }
    }
}

Zeigt ein komplexeres Layout mit einer kleinen Grafik neben einer Spalte mit Textelementen an

Mit Box kannst du Elemente übereinander platzieren. Mit Box lässt sich auch eine bestimmte Ausrichtung der darin enthaltenen Elemente konfigurieren.

@Composable
fun ArtistAvatar(artist: Artist) {
    Box {
        Image(bitmap = artist.image, contentDescription = "Artist image")
        Icon(Icons.Filled.Check, contentDescription = "Check mark")
    }
}

Zwei übereinander gestapelte Elemente

Oft sind diese Bausteine alles, was Sie brauchen. Sie können Ihre eigene zusammensetzbare Funktion schreiben, um diese Layouts zu einem ausgefeilteren Layout zu kombinieren, das zu Ihrer App passt.

Drei einfache Layout-Komponenten werden verglichen: Spalte, Zeile und Feld

Um die Position der untergeordneten Elemente innerhalb eines Row festzulegen, legen Sie die Argumente horizontalArrangement und verticalAlignment fest. Legen Sie für eine Column die Argumente verticalArrangement und horizontalAlignment fest:

@Composable
fun ArtistCardArrangement(artist: Artist) {
    Row(
        verticalAlignment = Alignment.CenterVertically,
        horizontalArrangement = Arrangement.End
    ) {
        Image(bitmap = artist.image, contentDescription = "Artist image")
        Column { /*...*/ }
    }
}

Elemente sind rechtsbündig ausgerichtet

Das Layoutmodell

Im Layoutmodell wird der UI-Baum in einem einzigen Durchlauf dargestellt. Jeder Knoten wird zuerst auf seine Größe geprüft und dann werden alle untergeordneten Knoten rekursiv gemessen. Dabei werden die Größenbeschränkungen an die untergeordneten Knoten im Baum weitergeleitet. Anschließend werden die Blätter dimensioniert und platziert. Die ermittelten Größen und Platzierungsanweisungen werden zurück zum Stamm des Baums übergeben.

Kurz gesagt: Eltern werden vor ihren Kindern gemessen, aber nach ihren Kindern ausgewählt und platziert.

Betrachten Sie die folgende SearchResult-Funktion.

@Composable
fun SearchResult() {
    Row {
        Image(
            // ...
        )
        Column {
            Text(
                // ...
            )
            Text(
                // ...
            )
        }
    }
}

Diese Funktion liefert den folgenden UI-Baum.

SearchResult
  Row
    Image
    Column
      Text
      Text

Im SearchResult-Beispiel folgt das Baumlayout der Benutzeroberfläche in dieser Reihenfolge:

  1. Der Stammknoten Row soll gemessen werden.
  2. Der Stammknoten Row bittet sein erstes untergeordnetes Element, Image, um eine Messung.
  3. Image ist ein Endknoten (d. h., er hat keine untergeordneten Elemente). Daher wird eine Größe angegeben und Platzierungsanweisungen zurückgegeben.
  4. Der Stammknoten Row bittet sein zweites Kind, Column, um eine Messung.
  5. Der Column-Knoten fordert die Messung seines ersten untergeordneten Text-Elements an.
  6. Der erste Text-Knoten ist ein Blattknoten. Er gibt also eine Größe an und gibt Platzierungsanweisungen zurück.
  7. Der Column-Knoten bittet sein zweites Text-Kind um eine Messung.
  8. Der zweite Text-Knoten ist ein Endknoten. Er gibt also eine Größe an und gibt Platzierungsanweisungen zurück.
  9. Nachdem der Knoten Column seine untergeordneten Elemente gemessen, ihre Größe festgelegt und platziert hat, kann er seine eigene Größe und Platzierung bestimmen.
  10. Nachdem der Stammknoten Row nun seine untergeordneten Elemente gemessen und die Größe angepasst hat, kann er seine eigene Größe und Platzierung bestimmen.

Reihenfolge von Messwerten, Größe und Platzierung im UI-Baum der Suchergebnisse

Leistung

Mit „Compose“ wird eine hohe Leistung erzielt, da Kinder nur einmal erfasst werden. Die Messung mit einem einzigen Durchlauf ist gut für die Leistung, da Compose tiefgehende UI-Baumstrukturen effizient verarbeiten kann. Wenn ein Element sein untergeordnetes Element zweimal misst und dieses untergeordnete Element jedes seiner untergeordneten Elemente zweimal misst usw., ist für das Layout einer gesamten Benutzeroberfläche viel Arbeit erforderlich, was die Leistung Ihrer App beeinträchtigt.

Wenn für Ihr Layout aus irgendeinem Grund mehrere Messungen erforderlich sind, bietet Compose ein spezielles System, systeminterne Messungen. Weitere Informationen zu dieser Funktion finden Sie unter Intrinsische Maße in Compose-Layouts.

Da Analyse und Platzierung unterschiedliche Teilphasen des Layout-Passes sind, können alle Änderungen, die sich nur auf das Platzieren von Elementen und nicht auf die Analyse auswirken, separat ausgeführt werden.

Modifikatoren in Layouts verwenden

Wie im Abschnitt Modifier zusammenstellen erläutert, können Sie Modifier verwenden, um Ihre Composeables zu verzieren oder zu ergänzen. Modifikatoren sind wichtig, um Ihr Layout anzupassen. Hier werden beispielsweise mehrere Modifikatoren verkettet, um die ArtistCard anzupassen:

@Composable
fun ArtistCardModifiers(
    artist: Artist,
    onClick: () -> Unit
) {
    val padding = 16.dp
    Column(
        Modifier
            .clickable(onClick = onClick)
            .padding(padding)
            .fillMaxWidth()
    ) {
        Row(verticalAlignment = Alignment.CenterVertically) { /*...*/ }
        Spacer(Modifier.size(padding))
        Card(
            elevation = CardDefaults.cardElevation(defaultElevation = 4.dp),
        ) { /*...*/ }
    }
}

Ein noch komplexeres Layout, bei dem mithilfe von Modifikatoren die Anordnung der Grafiken und die Bereiche geändert werden, die auf Nutzereingaben reagieren

Im obigen Code werden verschiedene Modifiziererfunktionen zusammen verwendet.

  • Mit clickable reagiert ein Composeable auf Nutzereingaben und zeigt eine Welle an.
  • padding fügt einem Element einen Abstand hinzu.
  • Mit fillMaxWidth wird für die zusammensetzbare Funktion die maximale Breite festgelegt, die ihr von ihrem übergeordneten Element zugewiesen wird.
  • Mit size() wird die bevorzugte Breite und Höhe eines Elements angegeben.

Scrollbare Layouts

Weitere Informationen zu scrollbaren Layouts finden Sie in der Dokumentation zu Eingabegesten.

Weitere Informationen zu Listen und Lazy-Listen finden Sie in der Dokumentation zum Erstellen von Listen.

Responsive Layouts

Ein Layout sollte so gestaltet sein, dass es für verschiedene Bildschirmausrichtungen und Formfaktoren geeignet ist. Compose bietet standardmäßig einige Mechanismen, mit denen sich Ihre zusammensetzbaren Layouts an verschiedene Bildschirmkonfigurationen anpassen lassen.

Einschränkungen

Um die Einschränkungen des übergeordneten Elements zu kennen und das Layout entsprechend zu gestalten, können Sie eine BoxWithConstraints verwenden. Die Messeinschränkungen finden Sie im Bereich der Content-Lambda. Sie können diese Messeinschränkungen verwenden, um verschiedene Layouts für verschiedene Bildschirmkonfigurationen zu erstellen:

@Composable
fun WithConstraintsComposable() {
    BoxWithConstraints {
        Text("My minHeight is $minHeight while my maxWidth is $maxWidth")
    }
}

Slotbasierte Layouts

Compose bietet eine große Auswahl an Material Design-basierten Compose-Elementen mit der Abhängigkeit androidx.compose.material:material, die beim Erstellen eines Compose-Projekts in Android Studio enthalten ist. So lässt sich die Benutzeroberfläche ganz einfach erstellen. Elemente wie Drawer, FloatingActionButton und TopAppBar sind verfügbar.

Materialkomponenten nutzen häufig Steckplatz-APIs, ein Muster, das in Compose eingeführt wurde, um eine Anpassungsebene auf Composables anzuwenden. Dieser Ansatz macht Komponenten flexibler, da sie ein untergeordnetes Element akzeptieren, das sich selbst konfigurieren kann, anstatt jeden Konfigurationsparameter des untergeordneten Elements offenlegen zu müssen. Slots lassen einen leeren Bereich in der Benutzeroberfläche, den der Entwickler nach Belieben füllen kann. In einem TopAppBar können Sie beispielsweise die folgenden Slots anpassen:

Ein Diagramm, das die verfügbaren Slots in der App-Leiste „Material Components“ zeigt

Für Composables wird in der Regel ein content-komposables Lambda ( content: @Composable () -> Unit) verwendet. Slot-APIs stellen für bestimmte Verwendungen mehrere content-Parameter bereit. Mit TopAppBar können Sie beispielsweise den Inhalt für title, navigationIcon und actions bereitstellen.

Mit Scaffold können Sie beispielsweise eine Benutzeroberfläche mit der grundlegenden Material Design-Layoutstruktur implementieren. Scaffold bietet Slots für die gängigsten Material-Komponenten der obersten Ebene, z. B. TopAppBar, BottomAppBar, FloatingActionButton und Drawer. Mit Scaffold können Sie ganz einfach dafür sorgen, dass diese Komponenten richtig positioniert sind und zusammen funktionieren.

Die Beispiel-App „JetNews“, in der mehrere Elemente mithilfe von Scaffold positioniert werden

@Composable
fun HomeScreen(/*...*/) {
    ModalNavigationDrawer(drawerContent = { /* ... */ }) {
        Scaffold(
            topBar = { /*...*/ }
        ) { contentPadding ->
            // ...
        }
    }
}