Grundlagen zum Verfassen von Layouts

Mit Jetpack Compose ist es viel einfacher, die Benutzeroberfläche Ihrer App zu entwerfen und zu erstellen. Compose wandelt den Status in UI-Elemente um, und zwar über:

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

Transformationsstatus über Komposition, Layout und Zeichnen in die Benutzeroberfläche einbinden

In diesem Dokument geht es um das Layout von Elementen. Es werden einige der Bausteine erläutert, die Compose für das Layout von UI-Elementen bietet.

Ziele von Layouts in Compose

Die Jetpack Compose-Implementierung des Layoutsystems hat zwei Hauptziele:

Grundlagen von zusammensetzbaren Funktionen

Komponierbare Funktionen sind die grundlegenden Bausteine von Compose. Eine zusammensetzbare Funktion ist eine Funktion, die Unit ausgibt und einen Teil der Benutzeroberfläche beschreibt. Die Funktion nimmt eine Eingabe entgegen und generiert das, was auf dem Bildschirm angezeigt wird. Weitere Informationen zu Composables finden Sie in der Dokumentation zum Compose-Mentalmodell.

Eine zusammensetzbare Funktion kann mehrere UI-Elemente ausgeben. Wenn Sie jedoch keine Angaben 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 Anleitung, wie sie angeordnet werden sollen, stapelt Compose die Textelemente übereinander, sodass sie unleserlich werden:

Zwei übereinander gezeichnete Textelemente, die den Text unleserlich machen

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

Standardlayoutkomponenten

In vielen Fällen können Sie einfach die Standard-Layoutelemente 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, die in einem Spaltenlayout angeordnet sind, damit der Text lesbar ist

Verwenden Sie Row, um Elemente horizontal auf dem Bildschirm zu 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

Verwenden Sie Box, um Elemente übereinander zu platzieren. Box unterstützt auch die Konfiguration einer bestimmten Ausrichtung der enthaltenen Elemente.

@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 eine eigene zusammensetzbare Funktion schreiben, um diese Layouts zu einem komplexeren Layout zu kombinieren, das zu Ihrer App passt.

Vergleicht drei einfache Layout-Kompositionen: Column, Row und Box

Wenn Sie die Position von untergeordneten Elementen innerhalb eines Row festlegen möchten, legen Sie die Argumente horizontalArrangement und verticalAlignment fest. Legen Sie für einen 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 die UI-Struktur in einem einzigen Durchgang angeordnet. Jeder Knoten wird zuerst aufgefordert, sich selbst zu messen und dann alle untergeordneten Elemente rekursiv zu messen. Dabei werden Größenbeschränkungen im Baum an untergeordnete Elemente weitergegeben. Anschließend werden die Blattknoten dimensioniert und platziert, wobei die aufgelösten Größen und Platzierungsanweisungen wieder im Baum nach oben weitergegeben werden.

Kurz gesagt: Übergeordnete Elemente werden vor ihren untergeordneten Elementen gemessen, aber nach ihren untergeordneten Elementen dimensioniert 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 Beispiel SearchResult folgt das Layout der UI-Struktur dieser Reihenfolge:

  1. Der Stammknoten Row wird aufgefordert, Messungen vorzunehmen.
  2. Der Stammknoten Row fordert sein erstes untergeordnetes Element Image auf, die Messung durchzuführen.
  3. Image ist ein Blattknoten (d. h. er hat keine untergeordneten Elemente), daher wird eine Größe gemeldet und es werden Platzierungsanweisungen zurückgegeben.
  4. Der Stammknoten Row fordert sein zweites untergeordnetes Element Column auf, die Messung durchzuführen.
  5. Der Column-Knoten fordert sein erstes untergeordnetes Text-Element auf, die Messung durchzuführen.
  6. Der erste Text-Knoten ist ein Blattknoten. Er meldet also eine Größe und gibt Platzierungsanweisungen zurück.
  7. Der Column-Knoten fordert sein zweites Text-untergeordnetes Element auf, die Messung durchzuführen.
  8. Der zweite Text-Knoten ist ein Blattknoten. Er meldet also eine Größe und gibt Platzierungsanweisungen zurück.
  9. Nachdem der Knoten Column seine untergeordneten Elemente gemessen, dimensioniert und platziert hat, kann er seine eigene Größe und Platzierung bestimmen.
  10. Nachdem der Stammknoten Row seine untergeordneten Elemente gemessen, dimensioniert und platziert hat, kann er seine eigene Größe und Platzierung bestimmen.

Reihenfolge von Messung, Größenbestimmung und Platzierung im UI-Baum für Suchergebnisse

Leistung

Compose erreicht eine hohe Leistung, indem untergeordnete Elemente nur einmal gemessen werden. Die Messung in einem Durchgang ist gut für die Leistung, da Compose so tiefe UI-Bäume effizient verarbeiten kann. Wenn ein Element sein untergeordnetes Element zweimal misst und dieses untergeordnete Element jedes seiner untergeordneten Elemente zweimal misst usw., müsste ein einzelner Versuch, eine gesamte Benutzeroberfläche zu gestalten, viel Arbeit erfordern, was es schwierig macht, die App-Leistung aufrechtzuerhalten.

Wenn für Ihr Layout aus irgendeinem Grund mehrere Messungen erforderlich sind, bietet Compose ein spezielles System, die intrinsischen 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 von Elementen und nicht auf die Messung auswirken, separat ausgeführt werden.

Modifikatoren in Layouts verwenden

Wie unter Compose-Modifikatoren beschrieben, können Sie Modifikatoren verwenden, um Ihre Composables zu dekorieren oder zu erweitern. Modifikatoren sind für die Anpassung des Layouts unerlässlich. Hier verketten wir beispielsweise mehrere Modifikatoren, um das 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 Modifikatoren verwendet werden, um die Anordnung der Grafiken und die Bereiche, die auf Nutzereingaben reagieren, zu ändern

Im Code oben werden verschiedene Modifikatorfunktionen zusammen verwendet.

  • clickable sorgt dafür, dass ein Compose-Element auf Nutzereingaben reagiert und einen Welleneffekt zeigt.
  • padding fügt Platz um ein Element herum hinzu.
  • Mit fillMaxWidth wird die maximale Breite des übergeordneten Elements für die komponierbare Funktion festgelegt.
  • size() gibt die bevorzugte Breite und Höhe eines Elements an.

Scrollbare Layouts

Weitere Informationen zu scrollbaren Layouts findest du in der Dokumentation zu Compose-Gesten.

Informationen zu Listen und Lazy-Listen finden Sie in der Dokumentation zu Compose-Listen.

Responsive layouts (Responsive-Layouts)

Ein Layout sollte unter Berücksichtigung verschiedener Bildschirmausrichtungen und Formfaktorgrößen entworfen werden. Compose bietet standardmäßig einige Mechanismen, mit denen Sie Ihre zusammensetzbaren Layouts an verschiedene Bildschirmkonfigurationen anpassen können.

Einschränkungen

Wenn Sie die Einschränkungen des übergeordneten Elements kennen und das Layout entsprechend gestalten möchten, können Sie ein BoxWithConstraints verwenden. Die Messbeschränkungen finden Sie im Bereich des Content-Lambdas. Mit diesen Einschränkungen können Sie verschiedene Layouts für unterschiedliche Bildschirmkonfigurationen erstellen:

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

Slotbasierte Layouts

Compose bietet eine Vielzahl von Composables, die auf Material Design basieren, mit der Abhängigkeit androidx.compose.material:material (enthalten, wenn ein Compose-Projekt in Android Studio erstellt wird), um die Erstellung von Benutzeroberflächen zu vereinfachen. Elemente wie Drawer, FloatingActionButton und TopAppBar sind alle vorhanden.

Material-Komponenten nutzen intensiv Slot APIs, ein von Compose eingeführtes Muster, um eine Anpassungsebene über den Composables zu ermöglichen. Dieser Ansatz macht Komponenten flexibler, da sie ein untergeordnetes Element akzeptieren, das sich selbst konfigurieren kann, anstatt jeden Konfigurationsparameter des untergeordneten Elements verfügbar machen 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 einer App-Leiste für Material-Komponenten zeigt

Komponierbare Funktionen verwenden normalerweise einen content Lambda-Ausdruck ( content: @Composable () -> Unit). Slot APIs stellen mehrere content-Parameter für bestimmte Verwendungszwecke bereit. Mit TopAppBar können Sie beispielsweise die Inhalte für title, navigationIcon und actions bereitstellen.

Mit Scaffold können Sie beispielsweise eine UI mit der grundlegenden Material Design-Layoutstruktur implementieren. Scaffold bietet Slots für die gängigsten Material-Komponenten auf oberster 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 korrekt zusammenarbeiten.

Die Beispiel-App JetNews, die Scaffold verwendet, um mehrere Elemente zu positionieren

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