Grundlagen zum Verfassen von Layouts

Jetpack Compose erleichtert das Entwerfen und Erstellen der Benutzeroberfläche Ihrer App erheblich. „Compose“ wandelt den Status mithilfe folgender Methoden in UI-Elemente um:

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

Schreiben, Umwandlung des Zustands in die Benutzeroberfläche durch Komposition, Layout und Zeichnung

In diesem Dokument geht es hauptsächlich um das Layout von Elementen. Außerdem werden einige der in Compose zur Verfügung gestellten Bausteine für das Layout Ihrer UI-Elemente erläutert.

Ziele von Layouts in Compose

Die Jetpack Compose-Implementierung des Layoutsystems hat zwei Hauptziele:

Grundlagen zusammensetzbarer 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 benötigt einige Eingaben und generiert das, was auf dem Bildschirm zu sehen ist. Weitere Informationen zu zusammensetzbaren Funktionen finden Sie in der Dokumentation zum Compose-ment-Modell.

Eine zusammensetzbare Funktion kann mehrere UI-Elemente ausgeben. Wenn Sie keine Vorgaben dazu machen, wie sie angeordnet werden sollen, werden die Elemente mit „Compose“ möglicherweise so angeordnet, wie Sie es möchten. Dieser Code generiert beispielsweise zwei Textelemente:

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

Ohne Anleitung, wie sie angeordnet sein sollen, stapelt „Compose“ die Textelemente übereinander, sodass sie unlesbar werden:

Zwei übereinander gezeichnete Textelemente, wodurch der Text unlesbar wird

Compose bietet eine Sammlung gebrauchsfertiger Layouts, mit denen Sie Ihre UI-Elemente anordnen können. Außerdem können Sie damit ganz einfach Ihre eigenen, stärker spezialisierten Layouts definieren.

Komponenten des Standardlayouts

In vielen Fällen können Sie einfach die Standard-Layoutelemente von Composer verwenden.

Mit Column kannst du Elemente vertikal auf dem Bildschirm platzieren.

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

Zwei Textelemente, die in einem Spaltenlayout angeordnet sind, 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 darin 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")
    }
}

Zeigt 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.

Vergleicht drei zusammensetzbare Funktionen des einfachen Layouts: 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 ein 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 rechts ausgerichtet

Layoutmodell

Im Layoutmodell wird der UI-Baum in einem einzigen Durchlauf dargestellt. Jeder Knoten wird zuerst aufgefordert, sich selbst zu messen. Anschließend werden alle untergeordneten Elemente rekursiv gemessen, wobei Größenbeschränkungen von der Baumstruktur an untergeordnete Elemente übergeben werden. Anschließend werden die Blattknoten in der Größe angepasst und platziert, wobei die aufgelösten Größen und die Platzierungsanweisungen an den Baum übergeben werden.

Kurz gesagt: Eltern messen vor ihren Kindern, werden jedoch in der Größe und nach ihren Kindern platziert.

Sehen Sie sich die folgende SearchResult-Funktion an.

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

Diese Funktion liefert den folgenden UI-Baum.

SearchResult
  Row
    Image
    Column
      Text
      Text

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

  1. Der Stammknoten Row wird zum Messen aufgefordert.
  2. Der Stammknoten Row fordert die Messung durch sein erstes untergeordnetes Element, Image, an.
  3. Image ist ein Blattknoten (d. h., er hat keine untergeordneten Elemente), also gibt er eine Größe an und gibt Platzierungsanweisungen zurück.
  4. Der Stammknoten Row fordert die Messung durch sein zweites untergeordnetes Element Column an.
  5. Der Column-Knoten fordert die Messung seines ersten untergeordneten Text-Elements an.
  6. Der erste Text-Knoten ist ein Blattknoten. Daher gibt er eine Größe an und gibt Placement-Anweisungen zurück.
  7. Der Column-Knoten fordert die Messung seines zweiten untergeordneten Text-Elements an.
  8. Der zweite Text-Knoten ist ein Blattknoten. Daher gibt er eine Größe an und gibt Placement-Anweisungen zurück.
  9. Nachdem der Column-Knoten nun die Größe und Position der untergeordneten Elemente gemessen und platziert hat, kann er seine eigene Größe und Platzierung bestimmen.
  10. Nachdem der Stammknoten Row nun seine untergeordneten Elemente gemessen, angepasst und in der Größe platziert hat, kann er seine eigene Größe und Platzierung bestimmen.

Reihenfolge von Messung, Größe und Platzierung in der Suchergebnisstruktur

Leistung

„Compose“ erzielt eine hohe Leistung, 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 gemessen hat, dieses untergeordnete Element jedoch jedes seiner untergeordneten Elemente zweimal erfasst hat usw., würde ein einziger Versuch, eine ganze UI zu gestalten, viel Arbeit leisten, was es schwierig macht, die Leistung Ihrer App aufrechtzuerhalten.

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 Messungen in Compose-Layouts.

Da Messung und Platzierung unterschiedliche Unterphasen des Layoutdurchlaufs sind, können alle Änderungen, die sich nur auf die Platzierung der Elemente und nicht auf die Messung auswirken, separat vorgenommen werden.

Modifikatoren in Layouts verwenden

Wie unter Modifikatoren verfassen erläutert, können Sie Ihre zusammensetzbaren Funktionen mit Modifikatoren gestalten oder erweitern. Modifikatoren sind wichtig, um Ihr Layout anzupassen. Hier verketten wir beispielsweise mehrere Modifikatoren, um 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, in denen auf Nutzereingaben reagiert wird, geändert wird.

Im obigen Code werden verschiedene Modifikatorfunktionen zusammen verwendet.

  • clickable sorgt dafür, dass eine zusammensetzbare Funktion auf Nutzereingaben reagiert und eine Welle zeigt.
  • padding platziert ein Element mit Platz.
  • Mit fillMaxWidth wird für die zusammensetzbare Funktion die maximale Breite festgelegt, die ihr von ihrem übergeordneten Element zugewiesen wird.
  • size() gibt die bevorzugte Breite und Höhe eines Elements an.

Scrollbare Layouts

Weitere Informationen zu scrollbaren Layouts finden Sie in der Dokumentation zum Schreiben von Touch-Gesten.

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

Responsive Layouts

Ein Layout sollte unter Berücksichtigung verschiedener Bildschirmausrichtungen und Formfaktorgrößen entworfen werden. Compose bietet sofort ein paar Mechanismen, mit denen sich zusammensetzbare Layouts an verschiedene Bildschirmkonfigurationen anpassen lassen.

Einschränkungen

Sie können BoxWithConstraints verwenden, um die Einschränkungen des übergeordneten Elements zu kennen und das Layout entsprechend zu gestalten. Die Messbeschränkungen finden Sie im Bereich des Lambda-Inhalts. 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")
    }
}

Anzeigenflächenbasierte Layouts

Compose bietet eine Vielzahl von zusammensetzbaren Funktionen basierend auf Material Design mit der Abhängigkeit androidx.compose.material:material (in Android Studio enthalten), die die Erstellung der UI erleichtert. Elemente wie Drawer, FloatingActionButton und TopAppBar sind alle angegeben.

Bei Materialkomponenten werden Slot-APIs in großem Umfang eingesetzt. Mit der Funktion „Pattern Compose“ wird zusätzlich zu den zusammensetzbaren Funktionen eine zusätzliche Anpassungsebene eingeführt. Dieser Ansatz macht Komponenten flexibler, da sie ein untergeordnetes Element akzeptieren, das sich selbst konfigurieren kann, anstatt jeden Konfigurationsparameter des untergeordneten Elements sichtbar zu machen. Anzeigenflächen lassen einen leeren Bereich auf der Benutzeroberfläche, den der Entwickler nach Bedarf füllen kann. Dies sind beispielsweise die Slots, die Sie in einem TopAppBar anpassen können:

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

Zusammensetzbare Elemente verwenden normalerweise ein content zusammensetzbares Lambda ( content: @Composable () -> Unit). Slot-APIs stellen mehrere content-Parameter für bestimmte Anwendungsfälle zur Verfügung. Mit TopAppBar können Sie beispielsweise den Inhalt für title, navigationIcon und actions angeben.

Mit Scaffold kannst du beispielsweise eine UI 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 lässt sich ganz einfach dafür sorgen, dass diese Komponenten richtig positioniert sind und richtig zusammenarbeiten.

Die Beispiel-App „JetNews“, die mithilfe von Scaffold mehrere Elemente positioniert

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