Navigation für responsive Benutzeroberflächen

Navigation ist der Prozess der Interaktion mit der Benutzeroberfläche einer App, um auf die Inhaltsziele der App zuzugreifen. Die Navigationsprinzipien von Android enthalten Richtlinien, mit denen Sie eine einheitliche, intuitive App-Navigation erstellen können.

Responsive UIs bieten responsive Inhaltsziele und enthalten oft verschiedene Arten von Navigationselementen als Reaktion auf Änderungen der Anzeigegröße, z. B. eine untere Navigationsleiste auf kleinen Displays, eine Navigationsleiste auf mittelgroßen Displays oder eine durchgehende Navigationsleiste auf großen Displays. Responsive UIs sollten jedoch weiterhin den Prinzipien der Navigation entsprechen.

Die Jetpack-Navigationskomponente implementiert die Prinzipien der Navigation und kann verwendet werden, um die Entwicklung von Apps mit responsiven Benutzeroberflächen zu erleichtern.

Abbildung 1. Maximierte, mittelgroße und kompakte Displays mit Navigationsleiste, Schienen und Leiste am unteren Rand.

Responsive Navigation auf der Benutzeroberfläche

Die Größe des Displayfensters einer App wirkt sich auf die Ergonomie und Nutzerfreundlichkeit aus. Mit Fenstergrößenklassen können Sie geeignete Navigationselemente wie Navigationsleisten, Schienen oder Leisten bestimmen und dort platzieren, wo sie für den Nutzer am besten zugänglich sind. In den Layoutrichtlinien von Material Design nehmen Navigationselemente einen festen Bereich am oberen Rand des Displays ein und können an den unteren Rand verschoben werden, wenn die App kompakt ist. Ihre Auswahl der Navigationselemente hängt vor allem von der Größe des App-Fensters und der Anzahl der Elemente ab, die das Element enthalten muss.

Fenstergrößenklasse Einige Elemente Viele Artikel
Kompakte Breite Navigationsleiste unten Navigationsleiste (vorne oder unten)
mittlere Breite Navigationsstreifen Navigationsleiste (vorne)
Breite nach Expansion Navigationsstreifen Persistente Navigationsleiste (an der Spitze)

In ansichtsbasierten Layouts können Layoutressourcendateien durch Haltepunkte der Fenstergröße qualifiziert werden, um verschiedene Navigationselemente für unterschiedliche Anzeigeabmessungen zu verwenden. Jetpack Compose kann Haltepunkte verwenden, die von der Fenstergrößen-API bereitgestellt werden, um programmatisch das Navigationselement zu ermitteln, das am besten für das App-Fenster geeignet ist.

Aufrufe

<!-- res/layout/main_activity.xml -->

<androidx.constraintlayout.widget.ConstraintLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <com.google.android.material.bottomnavigation.BottomNavigationView
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        ... />

    <!-- Content view(s) -->
</androidx.constraintlayout.widget.ConstraintLayout>


<!-- res/layout-w600dp/main_activity.xml -->

<androidx.constraintlayout.widget.ConstraintLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <com.google.android.material.navigationrail.NavigationRailView
        android:layout_width="wrap_content"
        android:layout_height="0dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        ... />

    <!-- Content view(s) -->
</androidx.constraintlayout.widget.ConstraintLayout>


<!-- res/layout-w1240dp/main_activity.xml -->

<androidx.constraintlayout.widget.ConstraintLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <com.google.android.material.navigation.NavigationView
        android:layout_width="wrap_content"
        android:layout_height="0dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        ... />

    <!-- Content view(s) -->
</androidx.constraintlayout.widget.ConstraintLayout>

Textvorschläge

// This method should be run inside a Composable function.
val widthSizeClass = calculateWindowSizeClass(this).widthSizeClass
// You can get the height of the current window by invoking heightSizeClass instead.

@Composable
fun MyApp(widthSizeClass: WindowWidthSizeClass) {
    // Select a navigation element based on window size.
    when (widthSizeClass) {
        WindowWidthSizeClass.Compact -> { CompactScreen() }
        WindowWidthSizeClass.Medium -> { MediumScreen() }
        WindowWidthSizeClass.Expanded -> { ExpandedScreen() }
    }
}

@Composable
fun CompactScreen() {
    Scaffold(bottomBar = {
                NavigationBar {
                    icons.forEach { item ->
                        NavigationBarItem(
                            selected = isSelected,
                            onClick = { ... },
                            icon = { ... })
                    }
                }
            }
        ) {
        // Other content
    }
}

@Composable
fun MediumScreen() {
    Row(modifier = Modifier.fillMaxSize()) {
        NavigationRail {
            icons.forEach { item ->
                NavigationRailItem(
                    selected = isSelected,
                    onClick = { ... },
                    icon = { ... })
            }
        }
        // Other content
    }
}

@Composable
fun ExpandedScreen() {
    PermanentNavigationDrawer(
        drawerContent = {
            icons.forEach { item ->
                NavigationDrawerItem(
                    icon = { ... },
                    label = { ... },
                    selected = isSelected,
                    onClick = { ... }
                )
            }
        },
        content = {
            // Other content
        }
    )
}

Ziele für responsive Inhalte

In einer responsiven UI muss sich das Layout jedes Inhaltsziels an Änderungen der Fenstergröße anpassen. Ihre App kann den Layoutabstand anpassen, Elemente neu positionieren, Inhalte hinzufügen oder entfernen oder UI-Elemente, einschließlich Navigationselemente, ändern. Weitere Informationen finden Sie unter UI zu responsiven Layouts migrieren und Unterstützung verschiedener Bildschirmgrößen.

Wenn die Größenänderungsereignisse von jedem einzelnen Ziel ordnungsgemäß verarbeitet werden, werden die Änderungen auf der Benutzeroberfläche isoliert. Der restliche App-Status, einschließlich der Navigation, ist davon nicht betroffen.

Die Navigation sollte nicht als Nebeneffekt von Änderungen der Fenstergröße auftreten. Erstellen Sie keine Inhaltsziele nur für unterschiedliche Fenstergrößen. Erstellen Sie beispielsweise keine unterschiedlichen Zielanwendungen für die verschiedenen Displays eines faltbaren Geräts.

Wenn Sie als Nebeneffekt von Änderungen der Fenstergröße navigieren, treten die folgenden Probleme auf:

  • Das alte Ziel (für die vorherige Fenstergröße) ist möglicherweise kurzzeitig sichtbar, bevor zum neuen Ziel navigiert wird
  • Zur Aufrechterhaltung der Reversierbarkeit (z. B. beim Auf- und Zuklappen eines Geräts) ist eine Navigation für jede Fenstergröße erforderlich.
  • Es kann schwierig sein, den App-Status zwischen Zielen beizubehalten, da die Navigation den Status nach dem Knacken des Backstacks zerstören kann.

Außerdem ist Ihre App möglicherweise nicht einmal im Vordergrund, während die Fenstergröße geändert wird. Für das Layout Ihrer App wird möglicherweise mehr Platz benötigt als für die App im Vordergrund. Wenn der Nutzer zu Ihrer App zurückkehrt, haben sich möglicherweise die Ausrichtung und die Fenstergröße geändert.

Wenn für Ihre App eindeutige Inhaltsziele basierend auf der Fenstergröße erforderlich sind, sollten Sie die relevanten Ziele in einem einzigen Ziel mit alternativen Layouts zusammenfassen.

Inhaltsziele mit alternativen Layouts

Als Teil eines responsiven Designs kann ein einzelnes Navigationsziel je nach Größe des App-Fensters alternative Layouts haben. Jedes Layout nimmt das gesamte Fenster ein, aber es werden verschiedene Layouts für unterschiedliche Fenstergrößen angezeigt.

Ein kanonisches Beispiel ist die Listenansicht. Bei kleinen Fenstergrößen zeigt Ihre App ein Inhaltslayout für die Liste und eins für die Details an. Wenn Sie zum Ziel der Listenansicht navigieren, wird anfangs nur das Listenlayout angezeigt. Wenn ein Listenelement ausgewählt wird, zeigt Ihre App das Layout mit den Details an und ersetzt die Liste. Wenn die Rückwärtssteuerung ausgewählt ist, wird das Listenlayout angezeigt, wodurch die Details ersetzt werden. Bei maximierten Fenstergrößen werden die Listen- und Detaillayouts jedoch nebeneinander angezeigt.

Aufrufe

Mit SlidingPaneLayout können Sie ein einzelnes Navigationsziel erstellen, bei dem auf großen Bildschirmen zwei Inhaltsbereiche nebeneinander angezeigt werden, auf Geräten mit kleinen Bildschirmen, z. B. Smartphones, aber jeweils nur ein Bereich.

<!-- Single destination for list and detail. -->

<navigation ...>

    <!-- Fragment that implements SlidingPaneLayout. -->
    <fragment
        android:id="@+id/article_two_pane"
        android:name="com.example.app.ListDetailTwoPaneFragment" />

    <!-- Other destinations... -->
</navigation>

Weitere Informationen zur Implementierung eines Layouts mit Listendetails mithilfe von SlidingPaneLayout finden Sie unter Layout mit zwei Bereichen erstellen.

Textvorschläge

In Compose kann eine Listen-Detailansicht implementiert werden, indem alternative zusammensetzbare Funktionen in einer einzigen route kombiniert werden, die Fenstergrößenklassen verwendet, um die entsprechende zusammensetzbare Funktion für jede Größenklasse auszugeben.

Eine Route ist der Navigationspfad zu einem Inhaltsziel, bei dem es sich in der Regel um eine einzelne zusammensetzbare Funktion handelt. Es kann sich aber auch um alternative zusammensetzbare Funktionen handeln. Die Geschäftslogik bestimmt, welche der alternativen zusammensetzbaren Funktionen angezeigt wird. Die zusammensetzbare Funktion füllt das App-Fenster aus, unabhängig davon, welche Alternative angezeigt wird.

Die Listenansicht besteht aus drei zusammensetzbaren Funktionen, zum Beispiel:

/* Displays a list of items. */
@Composable
fun ListOfItems(
    onItemSelected: (String) -> Unit,
) { /*...*/ }

/* Displays the detail for an item. */
@Composable
fun ItemDetail(
    selectedItemId: String? = null,
) { /*...*/ }

/* Displays a list and the detail for an item side by side. */
@Composable
fun ListAndDetail(
    selectedItemId: String? = null,
    onItemSelected: (String) -> Unit,
) {
  Row {
    ListOfItems(onItemSelected = onItemSelected)
    ItemDetail(selectedItemId = selectedItemId)
  }
}

Eine einzelne Navigationsroute bietet Zugriff auf die Listenansicht:

@Composable
fun ListDetailRoute(
    // Indicates that the display size is represented by the expanded window size class.
    isExpandedWindowSize: Boolean = false,
    // Identifies the item selected from the list. If null, a item has not been selected.
    selectedItemId: String?,
) {
  if (isExpandedWindowSize) {
    ListAndDetail(
      selectedItemId = selectedItemId,
      /*...*/
    )
  } else {
    // If the display size cannot accommodate both the list and the item detail,
    // show one of them based on the user's focus.
    if (selectedItemId != null) {
      ItemDetail(
        selectedItemId = selectedItemId,
        /*...*/
      )
    } else {
      ListOfItems(/*...*/)
    }
  }
}

ListDetailRoute (das Navigationsziel) bestimmt, welche der drei zusammensetzbaren Funktionen ausgegeben werden sollen: ListAndDetail für die Größe des maximierten Fensters; ListOfItems oder ItemDetail für „Schmal“, je nachdem, ob ein Listenelement ausgewählt wurde.

Die Route ist in einem NavHost enthalten. Beispiel:

NavHost(navController = navController, startDestination = "listDetailRoute") {
  composable("listDetailRoute") {
    ListDetailRoute(isExpandedWindowSize = isExpandedWindowSize,
                    selectedItemId = selectedItemId)
  }
  /*...*/
}

Sie können das Argument isExpandedWindowSize angeben, indem Sie WindowMetrics Ihrer Anwendung prüfen.

Das Argument selectedItemId kann von einem ViewModel bereitgestellt werden, der den Status über alle Fenstergrößen hinweg beibehält. Wenn der Nutzer ein Element aus der Liste auswählt, wird die Statusvariable selectedItemId aktualisiert:

class ListDetailViewModel : ViewModel() {

  data class ListDetailUiState(
      val selectedItemId: String? = null,
  )

  private val viewModelState = MutableStateFlow(ListDetailUiState())

  fun onItemSelected(itemId: String) {
    viewModelState.update {
      it.copy(selectedItemId = itemId)
    }
  }
}

val listDetailViewModel = ListDetailViewModel()

@Composable
fun ListDetailRoute(
    isExpandedWindowSize: Boolean = false,
    selectedItemId: String?,
    onItemSelected: (String) -> Unit = { listDetailViewModel.onItemSelected(it) },
) {
  if (isExpandedWindowSize) {
    ListAndDetail(
      selectedItemId = selectedItemId,
      onItemSelected = onItemSelected,
      /*...*/
    )
  } else {
    if (selectedItemId != null) {
      ItemDetail(
        selectedItemId = selectedItemId,
        /*...*/
      )
    } else {
      ListOfItems(
        onItemSelected = onItemSelected,
        /*...*/
      )
    }
  }
}

Die Route enthält auch ein benutzerdefiniertes BackHandler, wenn die zusammensetzbaren Elementdetails das gesamte App-Fenster belegen:

class ListDetailViewModel : ViewModel() {

  data class ListDetailUiState(
      val selectedItemId: String? = null,
  )

  private val viewModelState = MutableStateFlow(ListDetailUiState())

  fun onItemSelected(itemId: String) {
    viewModelState.update {
      it.copy(selectedItemId = itemId)
    }
  }

  fun onItemBackPress() {
    viewModelState.update {
      it.copy(selectedItemId = null)
    }
  }
}

val listDetailViewModel = ListDetailViewModel()

@Composable
fun ListDetailRoute(
    isExpandedWindowSize: Boolean = false,
    selectedItemId: String?,
    onItemSelected: (String) -> Unit = { listDetailViewModel.onItemSelected(it) },
    onItemBackPress: () -> Unit = { listDetailViewModel.onItemBackPress() },
) {
  if (isExpandedWindowSize) {
    ListAndDetail(
      selectedItemId = selectedItemId,
      onItemSelected = onItemSelected,
      /*...*/
    )
  } else {
    if (selectedItemId != null) {
      ItemDetail(
        selectedItemId = selectedItemId,
        /*...*/
      )
      BackHandler {
        onItemBackPress()
      }
    } else {
      ListOfItems(
        onItemSelected = onItemSelected,
        /*...*/
      )
    }
  }
}

Die Kombination des App-Status aus einer ViewModel mit Informationen zur Fenstergröße macht die Auswahl der geeigneten zusammensetzbaren Funktion durch einfache Logik möglich. Durch die Aufrechterhaltung eines unidirektionalen Datenflusses kann Ihre App den verfügbaren Platz optimal nutzen und gleichzeitig den Anwendungsstatus beibehalten.

Eine vollständige Implementierung der Listen-Detailansicht in Compose finden Sie im JetNews-Beispiel auf GitHub.

Ein Navigationsdiagramm

Um für eine einheitliche Nutzererfahrung auf allen Geräten und Fenstergrößen zu sorgen, sollten Sie ein einzelnes Navigationsdiagramm verwenden, bei dem das Layout jedes Inhaltsziels responsiv ist.

Wenn Sie für jede Fenstergrößenklasse ein anderes Navigationsdiagramm verwenden, müssen Sie bei jedem Wechsel der App von einer Größenklasse in eine andere das aktuelle Ziel des Nutzers in den anderen Grafiken ermitteln, einen Back-Stack erstellen und Statusinformationen abgleichen, die sich von den Diagrammen unterscheiden.

Verschachtelter Navigationshost

Ihre App kann ein Inhaltsziel enthalten, das eigene Inhaltsziele hat. In einer Listen-Detailansicht könnte der Element-Detailbereich beispielsweise UI-Elemente enthalten, die zu Inhalten navigieren, die das Element-Detail ersetzen.

Um diese Art der Unternavigation zu implementieren, kann der Detailbereich ein verschachtelter Navigationshost mit einem eigenen Navigationsdiagramm sein, das die Ziele angibt, auf die über den Detailbereich zugegriffen wird:

Aufrufe

<!-- layout/two_pane_fragment.xml -->

<androidx.slidingpanelayout.widget.SlidingPaneLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/sliding_pane_layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/list_pane"
        android:layout_width="280dp"
        android:layout_height="match_parent"
        android:layout_gravity="start"/>

    <!-- Detail pane is a nested navigation host. Its graph is not connected
         to the main graph that contains the two_pane_fragment destination. -->
    <androidx.fragment.app.FragmentContainerView
        android:id="@+id/detail_pane"
        android:layout_width="300dp"
        android:layout_weight="1"
        android:layout_height="match_parent"
        android:name="androidx.navigation.fragment.NavHostFragment"
        app:navGraph="@navigation/detail_pane_nav_graph" />
</androidx.slidingpanelayout.widget.SlidingPaneLayout>

Textvorschläge

@Composable
fun ItemDetail(selectedItemId: String? = null) {
    val navController = rememberNavController()
    NavHost(navController, "itemSubdetail1") {
        composable("itemSubdetail1") { ItemSubdetail1(...) }
        composable("itemSubdetail2") { ItemSubdetail2(...) }
        composable("itemSubdetail3") { ItemSubdetail3(...) }
    }
}

Dies unterscheidet sich von einer verschachtelten Navigationsgrafik, da die Navigationsgrafik der verschachtelten NavHost nicht mit der Hauptnavigationsgrafik verbunden ist. Das heißt, Sie können nicht direkt von Zielen in der einen Grafik zu Zielen in der anderen wechseln.

Weitere Informationen finden Sie unter Verschachtelte Navigationsgrafiken und Mit der Funktion „Schreiben“ navigieren.

Beibehaltener Status

Damit Ziele für responsive Inhalte bereitgestellt werden können, muss Ihre App ihren Status beibehalten, wenn das Gerät gedreht oder zugeklappt wird oder die Größe des App-Fensters angepasst wird. Standardmäßig werden durch solche Konfigurationsänderungen die Aktivitäten, Fragmente, Ansichtshierarchien und zusammensetzbaren Funktionen der Anwendung neu erstellt. Es wird empfohlen, den UI-Status mit einem ViewModel- oder rememberSaveable-Element zu speichern, das auch bei Konfigurationsänderungen erhalten bleibt. Weitere Informationen finden Sie unter UI-Status speichern und Status und Jetpack Compose.

Größenänderungen sollten umkehrbar sein, z. B. wenn der Nutzer das Gerät und dann wieder zurückdreht.

Responsive Layouts können verschiedene Inhalte in unterschiedlichen Fenstergrößen anzeigen. Daher müssen responsive Layouts häufig einen zusätzlichen, inhaltsbezogenen Status speichern, auch wenn der Zustand nicht für die aktuelle Fenstergröße anwendbar ist. Beispielsweise könnte ein Layout Platz bieten, um ein zusätzliches Scroll-Widget nur bei größeren Fensterbreiten anzuzeigen. Wenn ein Größenänderungsereignis dazu führt, dass die Fensterbreite zu klein wird, wird das Widget ausgeblendet. Wenn die App wieder die vorherigen Abmessungen verwendet, wird das Scroll-Widget wieder sichtbar und die ursprüngliche Scrollposition sollte wiederhergestellt werden.

ViewModel-Bereiche

Im Entwicklerleitfaden Zur Navigation-Komponente migrieren wird eine Architektur mit einer einzigen Aktivität empfohlen, in der Ziele als Fragmente und die zugehörigen Datenmodelle mithilfe von ViewModel implementiert werden.

Ein ViewModel ist immer einem Lebenszyklus zugeordnet. Sobald dieser Lebenszyklus dauerhaft endet, wird der ViewModel gelöscht und kann verworfen werden. Der Lebenszyklus, auf den sich das ViewModel bezieht, und somit, wie weit das ViewModel weitergegeben werden kann, hängt davon ab, welcher Property-Delegate zum Abrufen des ViewModel verwendet wird.

Im einfachsten Fall ist jedes Navigationsziel ein einzelnes Fragment mit einem vollständig isolierten UI-Status. Daher kann jedes Fragment den Property-Delegaten viewModels() verwenden, um eine ViewModel zu erhalten, die auf dieses Fragment beschränkt ist.

Um den UI-Status zwischen Fragmenten zu teilen, gehen Sie den ViewModel auf die Aktivität zu, indem Sie activityViewModels() in den Fragmenten aufrufen (die entsprechende Aktivität ist nur viewModels()). Dadurch können die Aktivität und alle an sie angehängten Fragmente die ViewModel-Instanz teilen. In einer Architektur mit nur einer Aktivität hält dieser ViewModel-Bereich jedoch effektiv so lange wie die Anwendung, sodass ViewModel im Arbeitsspeicher bleibt, auch wenn er nicht von Fragmenten verwendet wird.

Angenommen, Ihr Navigationsdiagramm enthält eine Folge von Fragmentzielen, die einen Bezahlvorgang darstellen, und der aktuelle Status des gesamten Bezahlvorgangs befindet sich in einer ViewModel, die von den Fragmenten gemeinsam genutzt wird. Die Einbeziehung des ViewModel auf die Aktivität ist nicht nur zu breit gefasst, sondern führt zu einem weiteren Problem: Wenn der Nutzer den Bezahlvorgang für eine Bestellung durchläuft und ihn dann für eine zweite Bestellung noch einmal durchläuft, wird für beide Bestellungen dieselbe Instanz der ViewModel für den Bezahlvorgang verwendet. Vor dem Bezahlvorgang bei der zweiten Bestellung müssen Sie die Daten aus der ersten Bestellung manuell löschen. Fehler können für die Nutzenden kostspielig sein.

Legen Sie stattdessen ViewModel auf ein Navigationsdiagramm in der aktuellen NavController fest. Erstellen Sie eine verschachtelte Navigationsgrafik für die Ziele, die Teil des Bezahlvorgangs sind. Verwenden Sie dann in jedem dieser Fragmentziele den Property-Delegaten navGraphViewModels() und übergeben Sie die ID des Navigationsdiagramms, um das gemeinsame ViewModel-Objekt abzurufen. Dadurch wird sichergestellt, dass die entsprechende Instanz von ViewModel verworfen wird, sobald der Nutzer den Bezahlvorgang beendet und das verschachtelte Navigationsdiagramm nicht berücksichtigt wird. Sie wird dann nicht für den nächsten Bezahlvorgang verwendet.

Aufgabenstellung Property-Bevollmächtigter Darf ViewModel teilen mit
Fragment Fragment.viewModels() Nur aktuelles Fragment
Aktivitäten Activity.viewModels()

Fragment.activityViewModels()

Aktivität und alle damit verbundenen Fragmente
Navigationsdiagramm Fragment.navGraphViewModels() Alle Fragmente in derselben Navigationsgrafik

Wenn du einen verschachtelten Navigationshost verwendest (siehe oben), können die Ziele in diesem Host ViewModels nicht mit Zielen außerhalb des Hosts teilen, wenn navGraphViewModels() verwendet wird, da die Grafiken nicht verbunden sind. In diesem Fall können Sie stattdessen den Umfang der Aktivität verwenden.

Hochgezogener Zustand

In „Compose“ können Sie den Status bei Änderungen der Fenstergröße mit State Hoisting beibehalten. Durch Heben des Zustands der zusammensetzbaren Elemente an eine höhere Position im Kompositionsbaum kann der Zustand beibehalten werden, auch wenn die zusammensetzbaren Elemente nicht mehr sichtbar sind.

Im Abschnitt Schreiben des Artikels Inhaltsziele mit alternativen Layouts haben wir den Status der zusammensetzbaren Funktionen der Listen-Detailansicht auf ListDetailRoute hochgestuft, damit der Status unabhängig davon erhalten bleibt, welche zusammensetzbare Funktion angezeigt wird:

@Composable
fun ListDetailRoute(
    // Indicates that the display size is represented by the expanded window size class.
    isExpandedWindowSize: Boolean = false,
    // Identifies the item selected from the list. If null, a item has not been selected.
    selectedItemId: String?,
) { /*...*/ }

Zusätzliche Ressourcen