Adaptive Apps mit Jetpack Compose erstellen

1. Einleitung

In diesem Codelab erfährst du, wie du adaptive Apps für Smartphones, Tablets und faltbare Geräte entwickelst und wie sie mit Jetpack Compose die Erreichbarkeit verbessern. Außerdem lernen Sie Best Practices für die Verwendung von Material 3-Komponenten und -Motiven kennen.

Bevor wir loslegen, sollten wir verstehen, was wir unter Anpassungsfähigkeit verstehen.

Anpassungsfähigkeit

Die Benutzeroberfläche Ihrer App sollte responsiv sein, um unterschiedliche Fenstergrößen, -ausrichtungen und Formfaktoren zu berücksichtigen. Ein adaptives Layout ändert sich je nach verfügbarer Bildschirmfläche. Dazu gehören einfache Layoutanpassungen, um den Platz zu füllen, den jeweiligen Navigationsstil auszuwählen oder das Layout komplett zu ändern, um mehr Platz zu nutzen.

Weitere Informationen finden Sie unter Adaptives Design.

In diesem Codelab erfahren Sie, wie Sie Jetpack Compose optimal an Ihre Anforderungen anpassen können. Sie entwickeln eine App namens „Antworten“, die Ihnen zeigt, wie Sie die Anpassungsfähigkeit für alle Arten von Bildschirmen implementieren und wie Anpassungsfähigkeit und Erreichbarkeit zusammenwirken, um Nutzenden eine optimale Erfahrung zu ermöglichen.

Lerninhalte

  • So konzipierst du deine App mit Jetpack Compose für alle Fenstergrößen.
  • So kannst du deine App auf verschiedene faltbare Geräte ausrichten.
  • Hier erfahren Sie, wie Sie verschiedene Navigationstypen für eine bessere Erreichbarkeit und Barrierefreiheit verwenden.
  • Hier erfährst du, wie du Material 3-Komponenten verwenden kannst, um für jede Fenstergröße das beste Erlebnis zu bieten.

Voraussetzungen

Für dieses Codelab verwenden Sie den Emulator für anpassbare Fenstergröße. Damit können Sie zwischen verschiedenen Gerätetypen und Fenstergrößen wechseln.

Emulator für die individuelle Größe: Smartphone, aufgeklappt, Tablet oder Desktop-Computer

Wenn Sie mit Compose nicht vertraut sind, sollten Sie das Codelab zu den Grundlagen von Jetpack Compose absolvieren, bevor Sie mit diesem Codelab beginnen.

Aufgaben

  • Eine interaktive E-Mail-Client-App, die Best Practices für anpassungsfähige Designs, verschiedene Material-Navigationen und eine optimale Nutzung der Bildschirmfläche verwendet.

Codelab: Support für mehrere Geräte

2. Einrichten

Klonen Sie das GitHub-Repository über die Befehlszeile, um den Code für dieses Codelab abzurufen:

git clone https://github.com/android/codelab-android-compose.git
cd codelab-android-compose/AdaptiveUiCodelab

Alternativ können Sie das Repository als ZIP-Datei herunterladen:

Wir empfehlen, mit dem Code im main-Branch zu beginnen und das Codelab Schritt für Schritt in Ihrem eigenen Tempo durchzugehen.

Projekt in Android Studio öffnen

  1. Wählen Sie im Fenster Willkommen bei Android Studio die Option c01826594f360d94.pngVorhandenes Projekt öffnen aus.
  2. Wählen Sie den Ordner <Download Location>/AdaptiveUiCodelab aus. Achten Sie darauf, dass Sie das Verzeichnis AdaptiveUiCodelab auswählen, das build.gradle enthält.
  3. Wenn Android Studio das Projekt importiert hat, testen Sie, ob Sie den main-Zweig ausführen können.

Startcode erkunden

Der main-Branch-Code enthält das ui-Paket. Sie werden mit den folgenden Dateien in diesem Paket arbeiten:

  • MainActivity.kt: Einstiegspunktaktivität, bei der Sie Ihre App starten.
  • ReplyApp.kt: enthält zusammensetzbare Funktionen der Hauptbildschirm-UI
  • ReplyHomeViewModel.kt: Enthält den Daten- und UI-Status für den App-Inhalt.
  • ReplyListContent.kt: enthält zusammensetzbare Funktionen zur Bereitstellung von Listen und Detailbildschirmen.

Zuerst konzentrieren Sie sich auf MainActivity.kt.

MainActivity.kt

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)

    setContent {
        ReplyTheme {
            val uiState by viewModel.uiState.collectAsStateWithLifecycle()
            ReplyApp(
                replyHomeUIState = uiState,
                onEmailClick = viewModel::setSelectedEmail
            )
        }
    }
}

Wenn Sie diese App auf einem Emulator mit anpassbarer Größe ausführen und verschiedene Gerätetypen ausprobieren, z. B. ein Smartphone oder Tablet, wird die Benutzeroberfläche einfach auf den vorgegebenen Bereich erweitert, anstatt den Platz auf dem Bildschirm auszunutzen oder Ergonomie der Erreichbarkeit zu bieten.

Startbildschirm auf Smartphone

Anfängliche gestreckte Ansicht auf einem Tablet

Sie aktualisieren ihn, um den Platz auf dem Bildschirm zu nutzen, die Usability zu erhöhen und die User Experience insgesamt zu verbessern.

3. Apps anpassbar machen

In diesem Abschnitt wird erläutert, was Apps anpassungsfähig macht und welche Komponenten Material 3 bietet, um dies zu vereinfachen. Außerdem werden die Arten von Bildschirmen und Status abgedeckt, auf die Sie Ihre Anzeigen ausrichten, wie Smartphones, Tablets, große Tablets und faltbare Geräte.

Sie beginnen mit den Grundlagen von Fenstergrößen, Falteinstellungen und verschiedenen Arten von Navigationsoptionen. Anschließend können Sie diese APIs in Ihrer App verwenden, um sie anpassungsfähiger zu machen.

Fenstergrößen

Android-Geräte gibt es in allen Formen und Größen – von Smartphones über faltbare Geräte bis hin zu Tablets und ChromeOS-Geräten. Damit möglichst viele Fenstergrößen unterstützt werden, muss Ihre Benutzeroberfläche responsiv und anpassungsfähig sein. Damit Sie den richtigen Schwellenwert für die Änderung der Benutzeroberfläche Ihrer App finden, haben wir Haltepunktwerte definiert, mit denen Geräte in vordefinierte Größenklassen unterteilt werden können (kompakt, mittel und erweitert), die als Fenstergrößenklassen bezeichnet werden. Dies sind bestimmte Haltepunkte für den Darstellungsbereich, die Ihnen beim Entwerfen, Entwickeln und Testen responsiver und adaptiver Anwendungslayouts helfen.

Die Kategorien wurden speziell ausgewählt, um ein ausgewogenes Verhältnis zwischen Einfachheit des Layouts und der Flexibilität zu schaffen, Ihre App für besondere Anwendungsfälle zu optimieren. Die Fenstergrößenklasse wird immer durch den für die App verfügbaren Bildschirmbereich bestimmt, der möglicherweise nicht den gesamten physischen Bildschirm für Multitasking oder andere Segmentierungen umfasst.

WindowWidthSizeClass für die kompakte, mittlere und maximierte Breite.

WindowHeightSizeClass für kompakte, mittlere und maximierte Höhe.

Sowohl Breite als auch Höhe werden separat klassifiziert. Daher verfügt Ihre App zu jedem Zeitpunkt über zwei Fenstergrößenklassen: eine für die Breite und eine für die Höhe. Aufgrund der Allgegenwärtigkeit des vertikalen Scrollens ist die verfügbare Breite in der Regel wichtiger als die verfügbare Höhe. Daher verwenden Sie auch Breitengrößenklassen.

Status der Faltung

Faltbare Geräte bieten aufgrund ihrer unterschiedlichen Größen und der vorhandenen Scharniere noch mehr Situationen, an die sich Ihre App anpassen kann. Scharniere können einen Teil des Displays verdecken, sodass dieser Bereich nicht für Inhalte geeignet ist. Sie können sich auch voneinander trennen, wenn das Gerät aufgeklappt ist, also zwei separate physische Displays haben.

Faltbare Körperhaltung, flach und halb aufgeklappt

Außerdem könnte der Nutzer auf das innere Display schauen, während das Scharnier teilweise geöffnet ist. Dies kann je nach Ausrichtung des Faltens zu unterschiedlichen physischen Haltungen führen: „Auf dem Tisch“ (horizontale Faltung, oben im Bild oben) und Buchhaltung (vertikal zusammengeklappt).

Weitere Informationen zu Klapphaltungen und Scharnieren

All dies sind Punkte, die Sie bei der Implementierung adaptiver Layouts, die faltbare Geräte unterstützen, berücksichtigen sollten.

Adaptive Informationen abrufen

Die adaptive-Bibliothek von Material3 bietet bequemen Zugriff auf Informationen über das Fenster, in dem Ihre App ausgeführt wird.

  1. Fügen Sie der Versionskatalogdatei Einträge für dieses Artefakt und seine Version hinzu:

gradle/libs.versions.toml

[versions]
material3Adaptive = "1.0.0-beta01"

[libraries]
androidx-material3-adaptive = { module = "androidx.compose.material3.adaptive:adaptive", version.ref = "material3Adaptive" }
  1. Fügen Sie in der Build-Datei des App-Moduls die neue Bibliotheksabhängigkeit hinzu und führen Sie dann eine Gradle-Synchronisierung durch:

app/build.gradle.kts

dependencies {

    implementation(libs.androidx.material3.adaptive)
}

Sie können jetzt in jedem zusammensetzbaren Bereich mit currentWindowAdaptiveInfo() ein WindowAdaptiveInfo-Objekt abrufen, das Informationen wie die aktuelle Fenstergrößenklasse und ob sich das Gerät in einem faltbaren Zustand befindet, z. B. „Auf dem Tisch“.

Du kannst das jetzt in MainActivity ausprobieren.

  1. Rufen Sie in onCreate() innerhalb des ReplyTheme-Blocks die Informationen für adaptives Fenster ab und zeigen Sie die Größenklassen in einer zusammensetzbaren Text-Funktion an. Sie können dies nach dem ReplyApp()-Element hinzufügen:

MainActivity.kt

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)

    setContent {
        ReplyTheme {
            val uiState by viewModel.uiState.collectAsStateWithLifecycle()
            ReplyApp(
                replyHomeUIState = uiState,
                onEmailClick = viewModel::setSelectedEmail
            )

            val adaptiveInfo = currentWindowAdaptiveInfo()
            val sizeClassText =
                "${adaptiveInfo.windowSizeClass.windowWidthSizeClass}\n" +
                "${adaptiveInfo.windowSizeClass.windowHeightSizeClass}"
            Text(
                text = sizeClassText,
                color = Color.Magenta,
                modifier = Modifier.padding(20.dp)
            )
        }
    }
}

Wenn Sie die App jetzt ausführen, werden die Fenstergrößenklassen angezeigt, die über den App-Inhalt gedruckt werden. Sie können sich gern ansehen, was Sie sonst noch in den adaptiven Informationen für das Fenster finden. Danach können Sie diese Text entfernen, da sie die App-Inhalte verdeckt und für die nächsten Schritte nicht erforderlich ist.

4. Dynamische Navigation

Jetzt passen Sie die Navigation der App an, wenn sich der Gerätestatus und die Größe ändern, um die Erreichbarkeit zu verbessern.

Erreichbarkeit beschreibt die Fähigkeit, in einer App zu navigieren oder eine Interaktion mit einer App zu initiieren, ohne dass extreme Handpositionen oder die Positionierung der Hand geändert werden müssen. Wenn Nutzer ein Smartphone halten, befinden sich ihre Finger normalerweise unten auf dem Display. Wenn Nutzer ein geöffnetes faltbares Gerät oder Tablet halten, liegen ihre Finger normalerweise nahe an den Seiten. Berücksichtigen Sie beim Entwerfen Ihrer App und bei der Entscheidung, wo Sie interaktive UI-Elemente in Ihrem Layout platzieren, die ergonomischen Auswirkungen verschiedener Bereiche des Bildschirms.

  • Welche Bereiche können bequem erreicht werden, wenn das Gerät gehalten wird?
  • Welche Bereiche können nur mit ausgestreckten Fingern erreicht werden, was unpraktisch ist?
  • Welche Bereiche sind schwer zu erreichen oder weit entfernt von dem Punkt, an dem der Nutzer das Gerät hält?

Die Navigation ist das Erste, mit dem Nutzende interagieren, und sie enthält wichtige Aktionen in Bezug auf kritische User Journeys, daher sollte sie in Bereichen platziert werden, die am einfachsten zu erreichen sind. Material stellt verschiedene Komponenten bereit, die Ihnen bei der Implementierung der Navigation helfen, abhängig von der Fenstergrößenklasse des Geräts.

Navigation am unteren Rand

Die Navigation am unteren Rand ist ideal für kompakte Größen, da wir das Gerät so halten, dass unser Daumen leicht alle unteren Navigations-Touchpoints erreichen kann. Verwende es, wenn du ein kompaktes Gerät oder ein faltbares Gerät im kompakten, zusammengeklappten Zustand hast.

Untere Navigationsleiste mit Elementen

Bei einer mittleren Fenstergröße ist der Navigationsstreifen ideal für eine gute Erreichbarkeit, da der Daumen natürlich auf die Seite des Geräts fällt. Sie können eine Navigationsleiste auch mit einer Navigationsleiste kombinieren, um weitere Informationen anzuzeigen.

Navigationsstreifen mit Elementen

Die Navigationsleiste bietet eine einfache Möglichkeit, detaillierte Informationen zu Navigationstabs aufzurufen. Sie ist auf Tablets oder größeren Geräten leicht zugänglich. Es gibt zwei Arten von Navigationsleisten: eine modale Navigationsleiste und eine permanente Navigationsleiste.

Modalnavigationsleiste

Für kompakte bis mittelgroße Smartphones und Tablets können Sie eine modale Navigationsleiste verwenden, die als Overlay über dem Inhalt eingeblendet oder ausgeblendet werden kann. Sie kann manchmal mit einer Navigationsschiene kombiniert werden.

Modale Navigationsleiste mit Elementen

Dauerhafte Navigationsleiste

Auf großen Tablets, Chromebooks und Desktop-Computern können Sie eine Navigationsleiste verwenden, um die Navigation unveränderlich zu gestalten.

Permanente Navigationsleiste mit Elementen

Dynamische Navigation implementieren

Jetzt können Sie zwischen verschiedenen Navigationstypen wechseln, wenn sich der Gerätestatus und die Größe ändern.

Derzeit zeigt die App unabhängig vom Gerätestatus immer ein NavigationBar unter dem Bildschirminhalt an. Stattdessen können Sie die „Material“-Komponente NavigationSuiteScaffold verwenden, um basierend auf Informationen wie der aktuellen Fenstergrößenklasse automatisch zwischen den verschiedenen Navigationskomponenten zu wechseln.

  1. Fügen Sie die Gradle-Abhängigkeit hinzu, um diese Komponente abzurufen. Aktualisieren Sie dazu den Versionskatalog und das Build-Skript der App und führen Sie dann eine Gradle-Synchronisierung durch:

gradle/libs.versions.toml

[versions]
material3AdaptiveNavSuite = "1.3.0-beta01"

[libraries]
androidx-material3-adaptive-navigation-suite = { module = "androidx.compose.material3:material3-adaptive-navigation-suite", version.ref = "material3AdaptiveNavSuite" }

app/build.gradle.kts

dependencies {

    implementation(libs.androidx.material3.adaptive.navigation.suite)
}
  1. Suchen Sie die zusammensetzbare Funktion ReplyNavigationWrapper() in ReplyApp.kt und ersetzen Sie Column und ihren Inhalt durch einen NavigationSuiteScaffold:

ReplyApp.kt

@Composable
private fun ReplyNavigationWrapperUI(
    content: @Composable () -> Unit = {}
) {
    var selectedDestination: ReplyDestination by remember {
        mutableStateOf(ReplyDestination.Inbox)
    }

    NavigationSuiteScaffold(
        navigationSuiteItems = {
            ReplyDestination.entries.forEach {
                item(
                    selected = it == selectedDestination,
                    onClick = { /*TODO update selection*/ },
                    icon = {
                        Icon(
                            imageVector = it.icon,
                            contentDescription = stringResource(it.labelRes)
                        )
                    },
                    label = {
                        Text(text = stringResource(it.labelRes))
                    },
                )
            }
        }
    ) {
        content()
    }
}

Das Argument navigationSuiteItems ist ein Block, mit dem Sie Elemente mit der Funktion item() hinzufügen können, ähnlich wie das Hinzufügen von Elementen in einer LazyColumn. Innerhalb der nachgestellten Lambda-Funktion ruft dieser Code das content() auf, das als Argument an ReplyNavigationWrapperUI() übergeben wurde.

Führen Sie die App im Emulator aus und versuchen Sie, zwischen Smartphone, faltbarem und Tablet zu wechseln. Die Navigationsleiste ändert sich in eine Navigationsleiste und wieder zurück.

Bei sehr großen Fenstern, etwa auf einem Tablet im Querformat, kann es sinnvoll sein, die permanente Navigationsleiste anzuzeigen. NavigationSuiteScaffold unterstützt die Anzeige einer permanenten Leiste, die jedoch in keinem der aktuellen WindowWidthSizeClass-Werte angezeigt wird. Dafür ist jedoch nur eine kleine Änderung nötig.

  1. Fügen Sie den folgenden Code direkt vor dem Aufruf von NavigationSuiteScaffold ein:

ReplyApp.kt

@Composable
private fun ReplyNavigationWrapperUI(
    content: @Composable () -> Unit = {}
) {
    var selectedDestination: ReplyDestination by remember {
        mutableStateOf(ReplyDestination.Inbox)
    }

    val windowSize = with(LocalDensity.current) {
        currentWindowSize().toSize().toDpSize()
    }
    val layoutType = if (windowSize.width >= 1200.dp) {
        NavigationSuiteType.NavigationDrawer
    } else {
        NavigationSuiteScaffoldDefaults.calculateFromAdaptiveInfo(
            currentWindowAdaptiveInfo()
        )
    }

    NavigationSuiteScaffold(
        layoutType = layoutType,
        ...
    ) {
        content()
    }
}

Mit diesem Code wird zuerst die Fenstergröße abgerufen und mithilfe von currentWindowSize() und LocalDensity.current in DP-Einheiten konvertiert. Anschließend wird die Fensterbreite verglichen, um den Layouttyp der Navigations-UI zu bestimmen. Wenn die Fensterbreite mindestens 1200.dp beträgt, wird NavigationSuiteType.NavigationDrawer verwendet. Andernfalls wird die Standardberechnung verwendet.

Wenn Sie die App erneut in Ihrem Emulator mit anpassbarer Größe ausführen und verschiedene Typen ausprobieren, beachten Sie, dass jedes Mal, wenn sich die Bildschirmkonfiguration ändert oder Sie ein faltbares Gerät aufklappen, die Navigation in den passenden Typ für diese Größe wechselt.

Zeigt Änderungen der Anpassungsfähigkeit für verschiedene Gerätegrößen.

Herzlichen Glückwunsch! Sie haben verschiedene Navigationstypen kennengelernt, um verschiedene Arten von Fenstergrößen und -zuständen zu unterstützen!

Im nächsten Abschnitt erfahren Sie, wie Sie den verbleibenden Bildschirmbereich nutzen können, anstatt ein Listenelement von Rand zu Rand zu strecken.

5. Bildschirmspeicherplatznutzung

Ganz gleich, ob Sie die App auf einem kleinen Tablet, einem aufgeklappten Gerät oder einem großen Tablet ausführen – der Bildschirm wird so gestreckt, dass der verbleibende Platz ausgefüllt wird. Sie sollten diesen Bildschirmbereich nutzen können, um mehr Informationen anzuzeigen, z. B. für diese App, um Nutzern auf derselben Seite E-Mails und Threads anzuzeigen.

Material 3 definiert drei kanonische Layouts, die jeweils Konfigurationen für Klassen für kompakte, mittlere und maximierte Fenster haben. Das kanonische Layout Listendetails eignet sich perfekt für diesen Anwendungsfall und ist beim Schreiben als ListDetailPaneScaffold verfügbar.

  1. Rufen Sie diese Komponente ab, indem Sie die folgenden Abhängigkeiten hinzufügen und eine Gradle-Synchronisierung durchführen:

gradle/libs.versions.toml

[versions]
material3Adaptive = "1.0.0-beta01"

[libraries]
androidx-material3-adaptive-layout = { module = "androidx.compose.material3.adaptive:adaptive-layout", version.ref = "material3Adaptive" }
androidx-material3-adaptive-navigation = { module = "androidx.compose.material3.adaptive:adaptive-navigation", version.ref = "material3Adaptive" }

app/build.gradle.kts

dependencies {

    implementation(libs.androidx.material3.adaptive.layout)
    implementation(libs.androidx.material3.adaptive.navigation)
}
  1. Suchen Sie die zusammensetzbare Funktion ReplyAppContent() in ReplyApp.kt. Sie zeigt derzeit nur den Listenbereich durch Aufrufen von ReplyListPane() an. Ersetzen Sie diese Implementierung durch ListDetailPaneScaffold, indem Sie den folgenden Code einfügen. Da dies eine experimentelle API ist, fügen Sie auch die Annotation @OptIn in die Funktion ReplyAppContent() ein:

ReplyApp.kt

@OptIn(ExperimentalMaterial3AdaptiveApi::class)
@Composable
fun ReplyAppContent(
    replyHomeUIState: ReplyHomeUIState,
    onEmailClick: (Email) -> Unit,
) {
    val navigator = rememberListDetailPaneScaffoldNavigator<Long>()

    ListDetailPaneScaffold(
        directive = navigator.scaffoldDirective,
        value = navigator.scaffoldValue,
        listPane = {
            ReplyListPane(replyHomeUIState, onEmailClick)
        },
        detailPane = {
            ReplyDetailPane(replyHomeUIState.emails.first())
        }
    )
}

Mit diesem Code wird zuerst ein Navigator mit rememberListDetailPaneNavigator() erstellt. Über den Navigator können Sie steuern, welcher Bereich angezeigt wird und welcher Inhalt in diesem Bereich dargestellt werden soll. Dies wird später noch erläutert.

ListDetailPaneScaffold zeigt zwei Bereiche an, wenn die Klasse für die Fensterbreite maximiert wird. Andernfalls wird ein Fenster oder das andere Fenster basierend auf den Werten angezeigt, die für zwei Parameter angegeben wurden: die Scaffold-Anweisung und den Scaffold-Wert. Um das Standardverhalten zu erhalten, verwendet dieser Code die Scaffold-Anweisung und den vom Navigator bereitgestellten Scaffold-Wert.

Die verbleibenden erforderlichen Parameter sind zusammensetzbare Lambdas für die Ausschnitte. Mit ReplyListPane() und ReplyDetailPane() (zu finden in ReplyListContent.kt) werden die Rollen der Listen- bzw. Detailbereiche ausgefüllt. ReplyDetailPane() erwartet ein E-Mail-Argument. Momentan wird in diesem Code die erste E-Mail-Adresse aus der Liste der E-Mail-Adressen in ReplyHomeUIState verwendet.

Führen Sie die App aus und wechseln Sie zur Emulator-Ansicht zu faltbarem oder Tablet (möglicherweise müssen Sie auch die Ausrichtung ändern), um das zweiteilige Layout zu sehen. Das sieht schon viel besser aus als zuvor.

Kommen wir nun zu einigen der gewünschten Verhaltensweisen dieses Bildschirms. Wenn der Nutzer in der Liste auf eine E-Mail tippt, sollte sie zusammen mit allen Antworten im Detailbereich angezeigt werden. Derzeit verfolgt die App nicht, welche E-Mail ausgewählt wurde. Das Tippen auf ein Element hat keine Auswirkungen. Diese Informationen sollten am besten zusammen mit dem Rest des UI-Status in ReplyHomeUIState aufbewahrt werden.

  1. Öffnen Sie ReplyHomeViewModel.kt und suchen Sie die Datenklasse ReplyHomeUIState. Fügen Sie eine Property für die ausgewählte E-Mail-Adresse mit dem Standardwert null hinzu:

ReplyHomeViewModel.kt

data class ReplyHomeUIState(
    val emails : List<Email> = emptyList(),
    val selectedEmail: Email? = null,
    val loading: Boolean = false,
    val error: String? = null
)
  1. In derselben Datei hat ReplyHomeViewModel eine setSelectedEmail()-Funktion, die aufgerufen wird, wenn der Nutzer auf einen Listeneintrag tippt. Ändern Sie diese Funktion, um den UI-Status zu kopieren und die ausgewählte E-Mail aufzuzeichnen:

ReplyHomeViewModel.kt

fun setSelectedEmail(email: Email) {
    _uiState.update {
        it.copy(selectedEmail = email)
    }
}

Sie sollten berücksichtigen, was passiert, bevor der Nutzer auf ein Element tippt und die ausgewählte E-Mail-Adresse null lautet. Was sollte im Detailbereich angezeigt werden? Dafür gibt es mehrere Möglichkeiten. Beispielsweise wird standardmäßig das erste Element in der Liste angezeigt.

  1. Ändern Sie in derselben Datei die Funktion observeEmails(). Wenn die Liste der E-Mail-Adressen geladen wird und der vorherige UI-Status keine E-Mail-Adresse enthielt, legen Sie ihn auf das erste Element fest:

ReplyHomeViewModel.kt

private fun observeEmails() {
    viewModelScope.launch {
        emailsRepository.getAllEmails()
            .catch { ex ->
                _uiState.value = ReplyHomeUIState(error = ex.message)
            }
            .collect { emails ->
                val currentSelection = _uiState.value.selectedEmail
                _uiState.value = ReplyHomeUIState(
                    emails = emails,
                    selectedEmail = currentSelection ?: emails.first()
                )
            }
    }
}
  1. Kehren Sie zu ReplyApp.kt zurück und verwenden Sie die ausgewählte E-Mail-Adresse (falls verfügbar), um den Inhalt des Detailbereichs auszufüllen:

ReplyApp.kt

ListDetailPaneScaffold(
    // ...
    detailPane = {
        if (replyHomeUIState.selectedEmail != null) {
            ReplyDetailPane(replyHomeUIState.selectedEmail)
        }
    }
)

Führen Sie die App erneut aus und stellen Sie den Emulator auf die Tablet-Größe um. Wenn Sie auf ein Listenelement tippen, wird der Inhalt des Detailbereichs aktualisiert.

Das funktioniert gut, wenn beide Bereiche sichtbar sind, aber wenn im Fenster nur ein Bereich sichtbar ist, passiert beim Tippen auf ein Element nichts. Versuchen Sie, die Emulator-Ansicht auf ein Smartphone oder ein faltbares Gerät im Hochformat umzustellen. Dabei wird nur der Listenbereich angezeigt, auch wenn Sie auf ein Element tippen. Das liegt daran, dass ListDetailPaneScaffold, obwohl die ausgewählte E-Mail-Adresse aktualisiert wird, den Listenbereich in diesen Konfigurationen weiterhin enthält.

  1. Um dies zu beheben, füge den folgenden Code ein, wie die Lambda-Funktion an ReplyListPane übergeben wird:

ReplyApp.kt

ListDetailPaneScaffold(
    // ...
    listPane = {
        ReplyListPane(
            replyHomeUIState = replyHomeUIState,
            onEmailClick = { email ->
                onEmailClick(email)
                navigator.navigateTo(ListDetailPaneScaffoldRole.Detail, email.id)
            }
        )
    },
    // ...
)

Bei dieser Lambda-Funktion wird der zuvor erstellte Navigator verwendet, um ein zusätzliches Verhalten hinzuzufügen, wenn auf ein Element geklickt wird. Es ruft die ursprüngliche Lambda-Funktion auf, die an diese Funktion übergeben wurde, und ruft dann auch navigator.navigateTo() auf, um anzugeben, welcher Bereich angezeigt werden soll. Jedem Bereich im Gerüst ist eine Rolle zugeordnet. Für den Detailbereich ist dies ListDetailPaneScaffoldRole.Detail. Bei kleineren Fenstern sieht es so aus, als wäre die App vorwärts navigiert.

Die App muss auch festlegen, was passiert, wenn der Nutzer die Schaltfläche „Zurück“ im Detailbereich drückt. Dieses Verhalten hängt davon ab, ob ein oder zwei Bereiche sichtbar sind.

  1. Unterstützen Sie die Rückwärtsnavigation, indem Sie den folgenden Code hinzufügen.

ReplyApp.kt

@OptIn(ExperimentalMaterial3AdaptiveApi::class)
@Composable
fun ReplyAppContent(
    replyHomeUIState: ReplyHomeUIState,
    onEmailClick: (Email) -> Unit,
) {
    val navigator = rememberListDetailPaneScaffoldNavigator<Long>()

    BackHandler(navigator.canNavigateBack()) {
        navigator.navigateBack()
    }

    ListDetailPaneScaffold(
        directive = navigator.scaffoldDirective,
        value = navigator.scaffoldValue,
        listPane = {
            AnimatedPane {
                ReplyListPane(
                    replyHomeUIState = replyHomeUIState,
                    onEmailClick = { email ->
                        onEmailClick(email)
                        navigator.navigateTo(ListDetailPaneScaffoldRole.Detail, email.id)
                    }
                )
            }
        },
        detailPane = {
            AnimatedPane {
                if (replyHomeUIState.selectedEmail != null) {
                    ReplyDetailPane(replyHomeUIState.selectedEmail)
                }
            }
        }
    )
}

Der Navigator kennt den vollständigen Status der ListDetailPaneScaffold, ob eine Rückwärtsnavigation möglich ist und was in all diesen Szenarien zu tun ist. Mit diesem Code wird eine BackHandler erstellt, die immer dann aktiviert ist, wenn der Navigator zurücknavigieren kann, und innerhalb der Lambda-Funktion navigateBack() aufrufen. Außerdem wird jeder Bereich in eine zusammensetzbare AnimatedPane()-Funktion eingebunden, um den Übergang zwischen den einzelnen Bereichen deutlicher zu gestalten.

Führen Sie die App noch einmal in einem Emulator mit anpassbarer Größe für die verschiedenen Gerätetypen aus. Wenn sich die Bildschirmkonfiguration ändert oder Sie ein faltbares Gerät aufklappen, ändern sich die Navigation und der Bildschirminhalt dynamisch, wenn sich der Gerätestatus ändert. Sie können auch auf E-Mails im Listenbereich tippen, um zu sehen, wie sich das Layout auf verschiedenen Bildschirmen verhält, indem Sie beide Bereiche nebeneinander anzeigen oder zwischen ihnen reibungslos animiert werden.

Zeigt Änderungen der Anpassungsfähigkeit für verschiedene Gerätegrößen.

Herzlichen Glückwunsch! Du hast deine App erfolgreich an alle Arten von Gerätestatus und -größen angepasst. Probieren Sie verschiedene Möglichkeiten aus, um die App auf faltbaren Geräten, Tablets oder anderen Mobilgeräten auszuführen.

6. Glückwunsch

Das wars! Sie haben das Lab erfolgreich abgeschlossen. Du hast dieses Codelab erfolgreich abgeschlossen und gelernt, wie du Apps mit Jetpack Compose flexibel machen kannst.

Sie haben gelernt, wie Sie die Größe und den Status eines Geräts prüfen und die Benutzeroberfläche, die Navigation und andere Funktionen Ihrer App entsprechend aktualisieren. Außerdem haben Sie gelernt, wie Anpassungsfähigkeit die Erreichbarkeit verbessert und die User Experience verbessert.

Nächste Schritte

Sehen Sie sich auch die anderen Codelabs im Pfad zum Schreiben an.

Beispiel-Apps und -Anwendungen

Referenzdokumente