Navigation 3 bietet ein leistungsstarkes und flexibles System zum Verwalten des UI-Ablaufs Ihrer App über Szenen. Mit Szenen können Sie hochgradig angepasste Layouts erstellen, sich an verschiedene Bildschirmgrößen anpassen und komplexe Multi-Pane-Anwendungen nahtlos verwalten.
Szenen verstehen
In Navigation 3 ist ein Scene die Grundeinheit, mit der eine oder mehrere NavEntry-Instanzen gerendert werden. Ein Scene ist ein separater visueller Zustand oder Abschnitt Ihrer Benutzeroberfläche, der die Anzeige von Inhalten aus Ihrem Backstack enthalten und verwalten kann.
Jede Scene-Instanz wird eindeutig durch ihren key und die Klasse des Scene selbst identifiziert. Diese eindeutige Kennung ist wichtig, da sie die Animation der obersten Ebene steuert, wenn sich die Scene ändert.
Die Scene-Schnittstelle hat die folgenden Eigenschaften:
key: Any: Eine eindeutige Kennung für diese bestimmteScene-Instanz. Dieser Schlüssel sorgt in Kombination mit der Klasse desScenefür die Unterscheidung, hauptsächlich für Animationszwecke.entries: List<NavEntry<T>>: Eine Liste vonNavEntry-Objekten, die vomSceneangezeigt werden. Wichtig: Wenn dasselbeNavEntrywährend eines Übergangs in mehrerenScenesangezeigt wird (z.B. bei einem Übergang mit gemeinsam genutzten Elementen), werden die Inhalte nur vom letzten Ziel-Scenegerendert, in dem sie angezeigt werden.previousEntries: List<NavEntry<T>>: Diese Property definiert dieNavEntrys, die sich ergeben, wenn von der aktuellenSceneaus eine „Zurück“-Aktion erfolgt. Dies ist wichtig, um den richtigen vorherigen Zustand für die Vorhersage zu berechnen. So kannNavDisplayden richtigen vorherigen Zustand, der möglicherweise eine Szene mit einer anderen Klasse, einem anderen Schlüssel oder beidem ist, vorhersagen und zu diesem wechseln.content: @Composable () -> Unit: Dies ist die zusammensetzbare Funktion, in der Sie definieren, wie dasSceneseinentriesund alle umgebenden UI-Elemente rendert, die für diesesScenespezifisch sind.metadata: Map<String, Any>: Stellt szenenspezifische Informationen für andere Bibliothekskomponenten wieNavDisplaybereit. Standardmäßig wird dermetadatades letztenNavEntryinentrieszurückgegeben.
equals und hashCode in benutzerdefinierten Szenen implementieren
Bei benutzerdefinierten Scene-Implementierungen müssen equals und hashCode korrekt implementiert werden, um Folgendes zu unterstützen:
- Übergänge zwischen Szenenstatus: Jedes Mal, wenn eine Szene anhand der Liste der Szenenstrategien berechnet wird, prüft
NavDisplay, ob die neue Szene mit der vorherigen übereinstimmt. Wenn eine Änderung erkannt wird, wird dieSeekableTransitionaktualisiert, die für den Übergang zwischen Szenen verwendet wird. Dies wirkt sich wiederum auf den Lebenszyklusstatus der Szenen aus. - Overlay-Verwaltung: Für
OverlayScenes (z. B. Dialogfelder) verwendetNavDisplaydas Szenenobjekt selbst alskey, um Overlay-Lebenszyklen und Endanimationen zu verfolgen.
Prüfen Sie, ob alle Eigenschaften, die den Inhalt der Szene definieren, z. B. key, entries und previousEntries, in den Implementierungen von equals und hashCode enthalten sind. Im Allgemeinen sollten Sie keine Callbacks (z. B. onBack) in die Implementierung dieser Methoden einfügen, da sie Instanzen ändern können, ohne den logischen Szenenstatus zu ändern.
Szenenstrategien verstehen
Ein SceneStrategy ist der Mechanismus, der bestimmt, wie eine bestimmte Liste von NavEntrys aus dem Backstack angeordnet und in ein Scene überführt werden soll. Wenn ein SceneStrategy die aktuellen Backstack-Einträge erhält, stellt es sich im Wesentlichen zwei wichtige Fragen:
- Kann ich aus diesen Einträgen eine
Sceneerstellen? Wenn dieSceneStrategyfeststellt, dass sie die angegebenenNavEntryverarbeiten und eine aussagekräftigeScene(z.B. einen Dialog oder ein Layout mit mehreren Bereichen) erstellen kann, wird fortgefahren. Andernfalls wirdnullzurückgegeben, sodass andere Strategien die Möglichkeit haben, eineScenezu erstellen. - Falls ja, wie sollte ich diese Einträge in der
Scene?anordnen? Sobald sich eineSceneStrategyzur Verarbeitung der Einträge verpflichtet, übernimmt sie die Verantwortung für die Erstellung einerSceneund die Definition der Darstellung der angegebenenNavEntrys in dieserScene.
Das Herzstück eines SceneStrategy ist die Methode calculateScene:
@Composable public fun calculateScene( entries: List<NavEntry<T>>, onBack: (count: Int) -> Unit, ): Scene<T>?
Diese Methode ist eine Erweiterungsfunktion für ein SceneStrategyScope, das das aktuelle List<NavEntry<T>> aus dem Backstack verwendet. Die Methode sollte ein Scene<T> zurückgeben, wenn sie aus den bereitgestellten Einträgen erfolgreich erstellt werden kann, oder ein null, wenn dies nicht möglich ist.
Die SceneStrategyScope ist für die Verwaltung aller optionalen Argumente verantwortlich, die die SceneStrategy möglicherweise benötigt, z. B. einen onBack-Callback.
Zusammenspiel von Szenen und Szenenstrategien
Die NavDisplay ist die zentrale zusammensetzbare Funktion, die den Backstack beobachtet und einen oder mehrere SceneStrategy verwendet, um die entsprechende Scene zu bestimmen und zu rendern.
Für den sceneStrategies-Parameter von NavDisplay wird eine Liste von SceneStrategy-Instanzen erwartet, die für die Berechnung des anzuzeigenden Scene verantwortlich sind. Wenn mit den bereitgestellten Strategien kein Scene berechnet wird, greift NavDisplay standardmäßig automatisch auf einen SinglePaneSceneStrategy zurück.
So läuft die Interaktion ab:
- Wenn Sie dem Backstack Schlüssel hinzufügen oder daraus entfernen (z.B. mit
backStack.add()oderbackStack.removeLastOrNull()), werden diese Änderungen vonNavDisplaybeobachtet. - Der
NavDisplayübergibt die aktuelle Liste derNavEntrys (abgeleitet von den Backstack-Schlüsseln) in der Reihenfolge an den konfiguriertensceneStrategiesund ruftcalculateScenefür jeden auf, bis einScenezurückgegeben wird. - Wenn
SceneStrategyerfolgreich einScenezurückgibt, rendertNavDisplaydascontentdiesesScene. DasNavDisplayverwaltet auch Animationen und die Vorhersage für die Zurück-Geste basierend auf den Attributen desScene.
Beispiel: Layout mit einem Bereich (Standardverhalten)
Das einfachste benutzerdefinierte Layout ist eine Einzelbereichsanzeige. Dies ist das Standardverhalten, wenn keine andere SceneStrategy Vorrang hat.
data class SinglePaneScene<T : Any>( override val key: Any, val entry: NavEntry<T>, override val previousEntries: List<NavEntry<T>>, ) : Scene<T> { override val entries: List<NavEntry<T>> = listOf(entry) override val content: @Composable () -> Unit = { entry.Content() } } /** * A [SceneStrategy] that always creates a 1-entry [Scene] simply displaying the last entry in the * list. */ public class SinglePaneSceneStrategy<T : Any> : SceneStrategy<T> { override fun SceneStrategyScope<T>.calculateScene(entries: List<NavEntry<T>>): Scene<T>? = SinglePaneScene( key = entries.last().contentKey, entry = entries.last(), previousEntries = entries.dropLast(1) ) }
Beispiel: Einfaches Layout mit Liste und Details (benutzerdefinierte Szene und Strategie)
In diesem Beispiel wird gezeigt, wie ein Layout mit Liste und Detailansicht erstellt wird, das auf Grundlage von zwei Bedingungen aktiviert wird:
- Die Fensterbreite ist ausreichend, um zwei Bereiche zu unterstützen (d.h. mindestens
WIDTH_DP_MEDIUM_LOWER_BOUND). - Der Backstack enthält Einträge, die ihre Unterstützung für die Anzeige in einem Listen-/Detail-Layout mithilfe bestimmter Metadaten deklariert haben.
Das folgende Snippet ist der Quellcode für ListDetailScene.kt und enthält sowohl ListDetailScene als auch ListDetailSceneStrategy:
// --- ListDetailScene --- /** * A [Scene] that displays a list and a detail [NavEntry] side-by-side in a 40/60 split. * */ data class ListDetailScene<T : Any>( override val key: Any, override val previousEntries: List<NavEntry<T>>, val listEntry: NavEntry<T>, val detailEntry: NavEntry<T>, ) : Scene<T> { override val entries: List<NavEntry<T>> = listOf(listEntry, detailEntry) override val content: @Composable (() -> Unit) = { Row(modifier = Modifier.fillMaxSize()) { Column(modifier = Modifier.weight(0.4f)) { listEntry.Content() } Column(modifier = Modifier.weight(0.6f)) { detailEntry.Content() } } } } @Composable fun <T : Any> rememberListDetailSceneStrategy(): ListDetailSceneStrategy<T> { val windowSizeClass = currentWindowAdaptiveInfo().windowSizeClass return remember(windowSizeClass) { ListDetailSceneStrategy(windowSizeClass) } } // --- ListDetailSceneStrategy --- /** * A [SceneStrategy] that returns a [ListDetailScene] if the window is wide enough, the last item * is the backstack is a detail, and before it, at any point in the backstack is a list. */ class ListDetailSceneStrategy<T : Any>(val windowSizeClass: WindowSizeClass) : SceneStrategy<T> { override fun SceneStrategyScope<T>.calculateScene(entries: List<NavEntry<T>>): Scene<T>? { if (!windowSizeClass.isWidthAtLeastBreakpoint(WIDTH_DP_MEDIUM_LOWER_BOUND)) { return null } val detailEntry = entries.lastOrNull()?.takeIf { it.metadata.contains(DetailKey) } ?: return null val listEntry = entries.findLast { it.metadata.contains(ListKey) } ?: return null // We use the list's contentKey to uniquely identify the scene. // This allows the detail panes to be displayed instantly through recomposition, rather than // having NavDisplay animate the whole scene out when the selected detail item changes. val sceneKey = listEntry.contentKey return ListDetailScene( key = sceneKey, previousEntries = entries.dropLast(1), listEntry = listEntry, detailEntry = detailEntry ) } object ListKey : NavMetadataKey<Boolean> object DetailKey : NavMetadataKey<Boolean> companion object { /** * Helper function to add metadata to a [NavEntry] indicating it can be displayed * as a list in the [ListDetailScene]. */ fun listPane() = metadata { put(ListKey, true) } /** * Helper function to add metadata to a [NavEntry] indicating it can be displayed * as a list in the [ListDetailScene]. */ fun detailPane() = metadata { put(DetailKey, true) } } }
Wenn Sie ListDetailSceneStrategy in Ihrem NavDisplay verwenden möchten, müssen Sie Ihre entryProvider-Aufrufe so ändern, dass sie ListDetailScene.listPane()-Metadaten für den Eintrag enthalten, der als list-Layout angezeigt werden soll, und ListDetailScene.detailPane() für den Eintrag, der als detail-Layout angezeigt werden soll. Geben Sie dann ListDetailSceneStrategy() als sceneStrategy an und verlassen Sie sich auf den Standard-Fallback für Szenarien mit einem Bereich:
// Define your navigation keys @Serializable data object ConversationList : NavKey @Serializable data class ConversationDetail(val id: String) : NavKey @Composable fun MyAppContent() { val backStack = rememberNavBackStack(ConversationList) val listDetailStrategy = rememberListDetailSceneStrategy<NavKey>() NavDisplay( backStack = backStack, onBack = { backStack.removeLastOrNull() }, sceneStrategies = listOf(listDetailStrategy), entryProvider = entryProvider { entry<ConversationList>( metadata = ListDetailSceneStrategy.listPane() ) { Column(modifier = Modifier.fillMaxSize()) { Text(text = "I'm a Conversation List") Button(onClick = { backStack.addDetail(ConversationDetail("123")) }) { Text(text = "Open detail") } } } entry<ConversationDetail>( metadata = ListDetailSceneStrategy.detailPane() ) { Text(text = "I'm a Conversation Detail") } } ) } private fun NavBackStack<NavKey>.addDetail(detailRoute: ConversationDetail) { // Remove any existing detail routes, then add the new detail route removeIf { it is ConversationDetail } add(detailRoute) }
Wenn Sie keine eigene Liste mit Details erstellen möchten, können Sie die Material-Liste mit Details verwenden. Sie enthält sinnvolle Details und unterstützt Platzhalter, wie im nächsten Abschnitt gezeigt.
Listen-Detailansicht-Inhalte in einer adaptiven Material-Szene anzeigen
Für den Anwendungsfall „Liste – Detail“ stellt das androidx.compose.material3.adaptive:adaptive-navigation3-Artefakt ein ListDetailSceneStrategy bereit, mit dem eine Scene für die Liste – Detail erstellt wird. Diese Scene übernimmt automatisch komplexe Anordnungen mit mehreren Bereichen (Liste, Detail und zusätzliche Bereiche) und passt sie an die Fenstergröße und den Gerätestatus an.
So erstellen Sie eine Materialliste – Detail Scene:
- Abhängigkeit hinzufügen: Fügen Sie
androidx.compose.material3.adaptive:adaptive-navigation3in die Dateibuild.gradle.ktsIhres Projekts ein. - Einträge mit
ListDetailSceneStrategy-Metadaten definieren: Verwenden SielistPane(), detailPane()undextraPane(), um IhreNavEntrysfür die richtige Bereichsdarstellung zu kennzeichnen. Mit demlistPane()-Helfer können Sie auch eindetailPlaceholderangeben, wenn kein Element ausgewählt ist. rememberListDetailSceneStrategy() verwenden: Diese zusammensetzbare Funktion stellt eine vorkonfigurierteListDetailSceneStrategybereit, die von einemNavDisplayverwendet werden kann.
Das folgende Snippet ist ein Beispiel für Activity, das die Verwendung von ListDetailSceneStrategy demonstriert:
@Serializable object ProductList : NavKey @Serializable data class ProductDetail(val id: String) : NavKey @Serializable data object Profile : NavKey class MaterialListDetailActivity : ComponentActivity() { @OptIn(ExperimentalMaterial3AdaptiveApi::class) override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { Scaffold { paddingValues -> val backStack = rememberNavBackStack(ProductList) val listDetailStrategy = rememberListDetailSceneStrategy<NavKey>() NavDisplay( backStack = backStack, modifier = Modifier.padding(paddingValues), onBack = { backStack.removeLastOrNull() }, sceneStrategies = listOf(listDetailStrategy), entryProvider = entryProvider { entry<ProductList>( metadata = ListDetailSceneStrategy.listPane( detailPlaceholder = { ContentYellow("Choose a product from the list") } ) ) { ContentRed("Welcome to Nav3") { Button(onClick = { backStack.add(ProductDetail("ABC")) }) { Text("View product") } } } entry<ProductDetail>( metadata = ListDetailSceneStrategy.detailPane() ) { product -> ContentBlue("Product ${product.id} ", Modifier.background(PastelBlue)) { Column(horizontalAlignment = Alignment.CenterHorizontally) { Button(onClick = { backStack.add(Profile) }) { Text("View profile") } } } } entry<Profile>( metadata = ListDetailSceneStrategy.extraPane() ) { ContentGreen("Profile") } } ) } } } }