Duyarlı kullanıcı arayüzleri için gezinme

Gezinme, bir uygulamanın içerik hedeflerine erişmek için kullanıcı arayüzü ile etkileşimde bulunma işlemidir. Android'in gezinme ilkeleri, tutarlı ve sezgisel bir gezinme deneyimi oluşturmanıza yardımcı olan yönergeler sağlar.

Duyarlı kullanıcı arayüzleri, duyarlı içerik hedefleri sağlar ve genellikle görüntü boyutu değişikliklerine yanıt olarak farklı gezinme öğeleri içerir. Örneğin, küçük ekranlarda bir alt gezinme çubuğu, orta boyutlu ekranlarda gezinme çubuğu veya büyük ekranlarda kalıcı bir gezinme çekmecesi vardır. Ancak duyarlı kullanıcı arayüzleri, gezinme ilkelerine yine de uyum sağlamalıdır.

Jetpack Gezinme bileşeni, gezinme ilkelerini uygular ve duyarlı kullanıcı arayüzleriyle uygulama geliştirmeyi kolaylaştırmak için kullanılabilir.

Şekil 1. Gezinme çekmecesi, korkuluk ve alt çubuklu genişletilmiş, orta ve kompakt ekranlar.

Duyarlı kullanıcı arayüzünde gezinme

Bir uygulamanın kapladığı ekran penceresinin boyutu, ergonomikliği ve kullanılabilirliği etkiler. Pencere boyutu sınıfları, uygun gezinme öğelerini (gezinme çubukları, korkuluklar veya çekmeceler gibi) belirlemenize ve bunları kullanıcının en kolay erişebileceği yerlere yerleştirmenize olanak tanır. Materyal Tasarım düzen yönergelerinde, gezinme öğeleri ekranın ön kenarında kalıcı bir alanı kaplar ve uygulamanın genişliği kompakt olduğunda alt kenara taşınabilir. Gezinme öğeleri seçiminiz büyük ölçüde uygulama penceresinin boyutuna ve öğenin içermesi gereken öğe sayısına bağlıdır.

Pencere boyutu sınıfı Birkaç öğe Birçok öğe
kompakt genişlik alt gezinme çubuğu gezinme çekmecesi (ön kenar veya alt)
orta genişlik navigasyon Rayı gezinme çekmecesi (ön kenar)
genişletilmiş genişlik navigasyon Rayı kalıcı gezinme çekmecesi (ön kenar)

Görünüme dayalı düzenlerde, düzen kaynak dosyaları farklı görüntüleme boyutları için farklı gezinme öğeleri kullanmak üzere pencere boyutu sınıfı ayrılma noktalarına göre nitelenebilir. Jetpack Compose, uygulama penceresine en uygun gezinme öğesini programatik olarak belirlemek için pencere boyutu sınıfı API'si tarafından sağlanan ayrılma noktalarını kullanabilir.

Görüntüleme sayısı

<!-- 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>

Oluştur

// 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
        }
    )
}

Duyarlı içerik hedefleri

Duyarlı kullanıcı arayüzünde, her içerik hedefinin düzeni pencere boyutundaki değişikliklere uyum sağlamalıdır. Uygulamanız düzen aralığını ayarlayabilir, öğeleri yeniden konumlandırabilir, içerik ekleyip kaldırabilir veya gezinme öğeleri de dahil olmak üzere kullanıcı arayüzü öğelerini değiştirebilir. (Kullanıcı arayüzünüzü duyarlı düzenlere taşıma ve Uyarlanabilir düzenler oluşturma bölümlerine bakın.)

Her hedef yeniden boyutlandırma etkinliklerini sorunsuzca işlediğinde değişiklikler kullanıcı arayüzünde izole edilir. Navigasyon da dahil olmak üzere uygulama durumunun geri kalanı bu durumdan etkilenmez.

Gezinme, pencere boyutu değişikliğinin yan etkisi olarak oluşmamalıdır. Farklı pencere boyutlarına uyum sağlamak için içerik hedefleri oluşturmayın. Örneğin, katlanabilir bir cihazın farklı ekranları için farklı içerik hedefleri oluşturmayın.

Pencere boyutu değişikliklerinin yan etkisi olarak gezinmenin aşağıdaki sorunları vardır:

  • Eski hedef (önceki pencere boyutu için), yeni hedefe gidilmeden önce kısa bir süre için görünebilir
  • Her pencere boyutuna geri döndürülebilirliği sağlamak (örneğin, katlanmış ve açılmış bir cihaz) için gezinme gereklidir
  • Hedefler arasında uygulama durumunun sürdürülmesi zor olabilir çünkü gezinmek arka yığını patlattıktan sonra durumu yok edebilir.

Ayrıca, pencere boyutu değişiklikleri gerçekleşirken uygulamanız ön planda bile olmayabilir. Uygulamanızın düzeni, ön plandaki uygulamadan daha fazla alan gerektirebilir. Kullanıcı uygulamanıza geri döndüğünde yön ve pencere boyutu tamamen değişmiş olabilir.

Uygulamanız pencere boyutuna göre benzersiz içerik hedefleri gerektiriyorsa alakalı hedefleri alternatif düzenler içeren tek bir hedefte birleştirmeyi düşünün.

Alternatif düzenlerin kullanıldığı içerik hedefleri

Duyarlı tasarımın parçası olarak, tek bir gezinme hedefinde uygulama pencere boyutuna bağlı olarak alternatif düzenler bulunabilir. Her düzen tüm pencereyi kaplar ancak farklı pencere boyutları için farklı düzenler sunulur.

Standart örnek olarak list-detail görünümü verilebilir. Küçük pencere boyutlarında, uygulamanız liste için bir içerik düzeni, ayrıntılar için de başka bir içerik düzeni görüntüler. Liste ayrıntıları görünümüne gidildiğinde başlangıçta yalnızca liste düzeni görüntülenir. Bir liste öğesi seçildiğinde uygulamanız, listenin yerine geçerek ayrıntı düzenini gösterir. Geri kontrolü seçildiğinde, ayrıntının yerine geçilerek liste düzeni gösterilir. Ancak, genişletilmiş pencere boyutlarında , liste ve ayrıntı düzenleri yan yana gösterilir.

Görüntüleme sayısı

SlidingPaneLayout, büyük ekranlarda iki içerik bölmesini yan yana görüntüleyen, ancak telefonlar gibi küçük ekranlı cihazlarda aynı anda yalnızca bir bölmeyi görüntüleyen tek bir gezinme hedefi oluşturmanıza olanak tanır.

<!-- 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>

SlidingPaneLayout kullanarak liste ayrıntısı düzeninin uygulanmasıyla ilgili ayrıntılar için İki bölmeli düzen oluşturma bölümüne bakın.

Oluştur

Compose'da bir liste ayrıntısı görünümü, her boyut sınıfı için uygun kompozisyonu oluşturmak üzere pencere boyutu sınıflarını kullanan alternatif kompozit öğelerin tek bir rotada birleştirilmesiyle uygulanabilir.

Rota, içerik hedefine giden gezinme yoludur. Bu genellikle tek bir içerikten oluşmuş olsa da alternatif olarak da oluşturulabilir. Alternatif kompozit öğelerden hangilerinin görüntüleneceğini iş mantığı belirler. Oluşturulabilir dosya, görüntülenen alternatiften bağımsız olarak uygulama penceresini doldurur.

Liste ayrıntısı görünümü üç birleştirilebilir görünümden oluşur, örneğin:

/* 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)
  }
}

Tek bir gezinme rotası, liste ayrıntıları görünümüne erişim sağlar:

@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 (gezinme hedefi), bir liste öğesinin seçili olup olmamasına bağlı olarak üç oluşturma öğesinden hangisinin yayınlanacağını belirler: genişletilmiş pencere boyutu için ListAndDetail; kompakt için ListOfItems veya ItemDetail.

Rota bir NavHost içindedir, örneğin:

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

isExpandedWindowSize bağımsız değişkenini, uygulamanızın WindowMetrics'ini inceleyerek sağlayabilirsiniz.

selectedItemId bağımsız değişkeni, tüm pencere boyutlarında durumu koruyan bir ViewModel tarafından sağlanabilir. Kullanıcı listeden bir öğe seçtiğinde selectedItemId durum değişkeni güncellenir:

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,
        /*...*/
      )
    }
  }
}

Oluşturulabilecek öğe ayrıntısı tüm uygulama penceresini kapladığında rota, özel bir BackHandler de içerir:

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,
        /*...*/
      )
    }
  }
}

Bir ViewModel öğesindeki uygulama durumunu pencere boyutu sınıfı bilgileriyle birleştirmek, uygun kompozisyonu seçmeyi basit bir mantık meselesi haline getirir. Tek yönlü veri akışını sürdürerek uygulamanız, uygulama durumunu korurken mevcut görüntüleme alanını tam olarak kullanabilir.

Compose'da eksiksiz liste-ayrıntı görünümü uygulaması için GitHub'daki JetNews örneğine bakın.

Bir gezinme grafiği

Tüm cihazlarda veya pencere boyutlarında tutarlı bir kullanıcı deneyimi sunmak için her içerik hedefinin düzeninin duyarlı olduğu tek bir gezinme grafiği kullanın.

Her pencere boyutu sınıfı için farklı bir gezinme grafiği kullanırsanız uygulama bir boyut sınıfından diğerine geçiş yaptığında, diğer grafiklerde kullanıcının geçerli hedefini belirlemeniz, bir arka yığın oluşturmanız ve grafikler arasında farklılık gösteren durum bilgilerini uzlaştırmanız gerekir.

İç içe yerleştirilmiş gezinme ana makinesi

Uygulamanız, kendi içerik hedefleri olan bir içerik hedefi içerebilir. Örneğin, liste ayrıntıları görünümünde öğe ayrıntıları bölmesi, öğe ayrıntısının yerini alan içeriğe giden kullanıcı arayüzü öğeleri içerebilir.

Bu tür bir alt gezinme uygulamak için ayrıntı bölmesi, ayrıntı bölmesinden erişilen hedefleri belirten, kendi gezinme grafiğine sahip olan iç içe yerleştirilmiş bir gezinme ana makinesi olabilir:

Görüntüleme sayısı

<!-- 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>

Oluştur

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

Bu, iç içe yerleştirilmiş NavHost gezinme grafiği ana gezinme grafiğine bağlı olmadığından, iç içe yerleştirilmiş gezinme grafiğinden farklıdır. Diğer bir deyişle, bir grafikteki hedeflerden diğerindeki hedeflere doğrudan gidemezsiniz.

Daha fazla bilgi için İç içe yerleştirilmiş gezinme grafikleri ve Oluşturma ile gezinme konularına bakın.

Korunma durumu

Duyarlı içerik hedefleri sağlamak için uygulamanızın cihaz döndürüldüğünde veya katlandığında ya da uygulama penceresi yeniden boyutlandırıldığında durumunu koruması gerekir. Varsayılan olarak, bu gibi yapılandırma değişiklikleri uygulamanın etkinliklerini, parçalarını, görünüm hiyerarşisini ve composables'ı yeniden oluşturur. Kullanıcı arayüzü durumunu kaydetmenin önerilen yolu, yapılandırma değişikliklerinde geçerli olan bir ViewModel veya rememberSaveable kullanmaktır. (Kullanıcı arayüzü durumlarını kaydetme ile State ve Jetpack Compose'a bakın.)

Boyut değişiklikleri tersine çevrilebilmelidir (örneğin, kullanıcı cihazı döndürüp tekrar döndürdüğünde).

Duyarlı düzenler, farklı pencere boyutlarında farklı içerik parçaları görüntüleyebilir. Bu nedenle, duyarlı düzenlerin, durum geçerli pencere boyutu için geçerli olmasa bile genellikle içerikle ilgili ek durum kaydetmesi gerekir. Örneğin, bir düzende ek kaydırma widget'ını yalnızca daha geniş pencere genişliklerinde göstermek için alan bulunabilir. Bir yeniden boyutlandırma etkinliği pencere genişliğinin çok küçük olmasına neden olursa widget gizlenir. Uygulama önceki boyutlarına göre yeniden boyutlandırıldığında, kaydırma widget'ı tekrar görünür hale gelir ve orijinal kaydırma konumunun geri yüklenmesi gerekir.

ViewModel kapsamları

Gezinme bileşenine taşıma geliştirici kılavuzunda, hedeflerin parça olarak, veri modellerinin ise ViewModel kullanılarak uygulandığı tek etkinlikli bir mimari önerilir.

ViewModel her zaman bir yaşam döngüsüne dahil edilir ve bu yaşam döngüsü kalıcı olarak sona erdiğinde ViewModel temizlenir ve silinebilir. ViewModel öğesinin kapsamının bulunduğu yaşam döngüsü (ve dolayısıyla ViewModel öğesinin ne kadar geniş bir şekilde paylaşılabileceği), ViewModel öğesini edinmek için hangi mülk yetkisinin kullanıldığına bağlıdır.

En basit durumda, her gezinme hedefi tamamen izole bir kullanıcı arayüzü durumuna sahip tek bir parçadır. Bu nedenle, her bir parça, kapsamı bu parçaya ayarlanmış bir ViewModel elde etmek için viewModels() özellik temsilcisini kullanabilir.

Kullanıcı arayüzü durumunu parçalar arasında paylaşmak için, parçalarda activityViewModels() yöntemini çağırarak ViewModel kapsamını etkinliğe dahil edin (etkinliğin eşdeğeri yalnızca viewModels()'dir). Bu, etkinliğin ve ona eklenen tüm parçaların ViewModel örneğini paylaşmasına izin verir. Bununla birlikte, tek etkinlikli bir mimaride bu ViewModel kapsamı, uygulama süresince etkili bir şekilde çalışır. Böylece ViewModel, hiçbir parça tarafından kullanılmasa bile bellekte kalır.

Gezinme grafiğinizde, ödeme akışını temsil eden parça hedefleri dizisi olduğunu ve ödeme deneyiminin tamamı için geçerli durumun parçalar arasında paylaşılan ViewModel içinde olduğunu varsayalım. ViewModel kapsamının etkinlik için belirlenmesi yalnızca çok geniş kapsamlı değildir, aynı zamanda başka bir sorunu da beraberinde getirir: Kullanıcı, bir sipariş için ödeme akışından geçer ve ardından ikinci bir sipariş için tekrar geçerse her iki sipariş de aynı ödeme ViewModel örneğini kullanır. İkinci sipariş ödemesinden önce, ilk siparişteki verileri manuel olarak temizlemeniz gerekir ve olası hatalar kullanıcı için maliyetli olabilir.

Bunun yerine, ViewModel kapsamını mevcut NavController içindeki bir gezinme grafiğine ayarlayın. Ödeme akışının parçası olan hedefleri iç içe geçmiş bir gezinme grafiği oluşturarak oluşturun. Ardından, bu parça hedeflerinin her birinde navGraphViewModels() mülk yetkisini kullanın ve paylaşılan ViewModel öğesini elde etmek için gezinme grafiğinin kimliğini iletin. Bu, kullanıcı ödeme akışından çıktıktan ve iç içe yerleştirilmiş gezinme grafiği kapsam dışında olduğunda, karşılık gelen ViewModel örneğinin silinmesini ve bir sonraki ödeme için kullanılmamasını sağlar.

Kapsam Mülk delegesi ViewModel şunlarla paylaşılabilir:
Parça Fragment.viewModels() Yalnızca geçerli parça
Etkinlik Activity.viewModels()

Fragment.activityViewModels()

Etkinlik ve ona bağlı tüm parçalar
Gezinme grafiği Fragment.navGraphViewModels() Aynı gezinme grafiğindeki tüm parçalar

İç içe yerleştirilmiş bir gezinme ana makinesi kullanıyorsanız (yukarıya bakın) bu ana makinedeki hedeflerin, grafikler bağlı olmadığından navGraphViewModels() kullanılırken ana makinenin dışındaki hedeflerle ViewModel paylaşamayacağını unutmayın. Bu durumda, bunun yerine etkinliğin kapsamını kullanabilirsiniz.

Kaldırılmış durumu

Compose'da eyalet yükseltme sayesinde pencere boyutu değişiklikleri sırasında eyaleti koruyabilirsiniz. Birleştirilebilecek öğelerin durumu, beste ağacında daha yüksek bir konuma yükseltildiğinde, besteler artık görünür olmasa bile durum korunabilir.

Yukarıdaki Alternatif düzenlerle içerik hedefleri'nin Oluşturma bölümünde, liste ayrıntısı görünümü kompozisyonlarının durumunu ListDetailRoute değerine çıkardık. Böylece, hangi kompozisyonun gösterildiğinden bağımsız olarak durum korunur:

@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?,
) { /*...*/ }

Ek kaynaklar