1. Einführung
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
- Die aktuelle stabile Version von Android Studio
- Ein virtuelles Gerät mit Android 13 mit anpassbarer Größe.
- Kotlin-Kenntnisse
- Grundlegendes Verständnis von Compose (z. B. die Annotation
@Composable
) - Grundkenntnisse in „Compose“-Layouts (z.B.
Row
undColumn
). - Grundlegende Kenntnisse über Modifikatoren (z.B.
Modifier.padding()
).
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.
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 namens Reply mit Best Practices für anpassbare Designs, verschiedene Material-Navigationen und eine optimale Nutzung von Bildschirmfläche.
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
- Wählen Sie im Fenster Willkommen bei Android Studio die Option
Vorhandenes Projekt öffnen aus.
- Wählen Sie den Ordner
<Download Location>/AdaptiveUiCodelab
aus. Achten Sie darauf, dass Sie das VerzeichnisAdaptiveUiCodelab
auswählen, dasbuild.gradle
enthält. - 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-UIReplyHomeViewModel.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.
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.
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.
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 für die Darstellung von Inhalten ungeeignet ist. Sie können sich auch voneinander trennen. Das heißt, dass beim Aufklappen des Geräts zwei separate physische Displays angezeigt werden.
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.
- 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" }
- 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.
- Rufen Sie in
onCreate()
innerhalb desReplyTheme
-Blocks die adaptiven Fensterinformationen ab und zeigen Sie die Größenklassen in einer zusammensetzbarenText
-Funktion an. Dies kannst du nach demReplyApp()
-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(
WindowInsets.safeDrawing.asPaddingValues()
)
)
}
}
}
Wenn Sie die App jetzt ausführen, werden die Fenstergrößenklassen angezeigt, die über den App-Inhalt gedruckt werden. Sehen Sie sich einfach an, was Sie sonst noch über die adaptiven Fensterinformationen erfahren können. Danach kannst du diese Text
entfernen, da sie den App-Inhalt 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 Ihre App nutzerfreundlicher zu machen.
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. Ihre Nutzer sollten in der Lage sein, in einer App zu navigieren oder eine Interaktion mit ihr zu initiieren, ohne dass extreme Handpositionen oder das Ändern der Handposition erforderlich sind.
Wenn Sie Ihre App entwerfen und entscheiden, wo Sie interaktive UI-Elemente in Ihrem Layout platzieren, berücksichtigen Sie 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. Die adaptive Material-Bibliothek bietet mehrere Komponenten, mit denen Sie je nach Fenstergrößenklasse des Geräts die Navigation implementieren können.
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.
Navigationsstreifen
Bei einer mittelbreiten 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.
Navigationsleiste
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.
Dauerhafte Navigationsleiste
Auf großen Tablets, Chromebooks und Desktop-Computern können Sie eine Navigationsleiste verwenden, um die Navigation unveränderlich zu gestalten.
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.
- 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)
}
- Suchen Sie die zusammensetzbare Funktion
ReplyNavigationWrapper()
inReplyApp.kt
und ersetzen SieColumn
und ihren Inhalt durch einenNavigationSuiteScaffold
:
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.
- 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.
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 erweiterte Fenster haben. Das kanonische Layout Listendetails eignet sich perfekt für diesen Anwendungsfall und ist beim Schreiben als ListDetailPaneScaffold
verfügbar.
- Rufen Sie diese Komponente ab, indem Sie die folgenden Abhängigkeiten hinzufügen und eine Gradle-Synchronisierung durchführen:
gradle/libs.versions.toml
[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)
}
- Suchen Sie die zusammensetzbare Funktion
ReplyAppContent()
inReplyApp.kt
. Sie zeigt derzeit nur den Listenbereich durch Aufrufen vonReplyListPane()
an. Ersetzen Sie diese Implementierung durchListDetailPaneScaffold
, indem Sie den folgenden Code einfügen. Da dies eine experimentelle API ist, fügen Sie auch die Annotation@OptIn
in die FunktionReplyAppContent()
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.
- Öffnen Sie
ReplyHomeViewModel.kt
und suchen Sie die DatenklasseReplyHomeUIState
. Fügen Sie eine Property für die ausgewählte E-Mail-Adresse mit dem Standardwertnull
hinzu:
ReplyHomeViewModel.kt
data class ReplyHomeUIState(
val emails : List<Email> = emptyList(),
val selectedEmail: Email? = null,
val loading: Boolean = false,
val error: String? = null
)
- In derselben Datei hat
ReplyHomeViewModel
einesetSelectedEmail()
-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.
- Ä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()
)
}
}
}
- 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.
- 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.
- 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 Funktion AnimatedPane()
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.
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
Glückwunsch! 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.
Beispielapps
- Die Beispiele für die Zusammensetzung sind eine Sammlung vieler Apps, die die in Codelabs erläuterten Best Practices beinhalten.