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

Gezinme, bir uygulamanın içerik hedeflerine erişmek için uygulamanın kullanıcı arayüzüyle etkileşim kurma sürecidir. Android'in gezinme ilkeleri; uygulamalarda 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 sunar ve genellikle görüntü boyutu değişikliklerine yanıt olarak farklı türde gezinme öğeleri içerir. Örneğin, küçük ekranlarda alt gezinme çubuğu, orta boyutlu ekranlarda gezinme bölmesi veya büyük ekranlarda kalıcı bir gezinme çekmecesi. Ancak duyarlı kullanıcı arayüzleri, gezinme ilkelerine uymaya devam etmelidir.

Jetpack Gezinme bileşeni, gezinme ilkelerini uygular ve duyarlı kullanıcı arayüzlerine sahip uygulamaların geliştirilmesini kolaylaştırmak için kullanılabilir.

Şekil 1. Gezinme çekmecesi, çubuk ve alt çubuk içeren genişletilmiş, orta ve kompakt ekranlar.

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

Bir uygulamanın kapladığı ekran penceresinin boyutu, ergonomiyi ve kullanılabilirliği etkiler. Pencere boyutu sınıfları, uygun gezinme öğelerini (gezinme çubukları, korkuluklar veya çekmeceler gibi) belirlemenize ve bunları kullanıcı için en erişilebilir 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 küçük 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ı Az sayıda öğe Birçok öğe
en yüksek genişlik alt gezinme çubuğu gezinme çekmecesi (ön kenar veya alt)
orta genişlik gezinme çubuğu gezinme çekmecesi (ön kenar)
genişletilmiş genişlik gezinme çubuğu kalıcı gezinme çekmecesi (ön kenar)

Görünüme dayalı düzenlerde, düzen kaynak dosyaları, farklı görüntü boyutları için farklı gezinme öğeleri kullanmak amacıyla pencere boyutu sınıfı ayrılma noktalarına göre değerlendirilebilir. Jetpack Compose, uygulama penceresine en uygun gezinme öğesini programatik olarak belirlemek için window size class API 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ü duyarlı düzenlere taşıma ve Farklı ekran boyutlarını destekleme konularına bakın.)

Her bir hedef yeniden boyutlandırma etkinliklerini sorunsuz bir şekilde ele aldığında değişiklikler kullanıcı arayüzünde izole edilir. Gezinme de dahil olmak üzere uygulama durumunun geri kalanı etkilenmez.

Gezinme, pencere boyutu değişikliklerinin yan etkisi olarak gerçekleşmemelidir. Yalnızca farklı pencere boyutlarına uyum sağlamak için içerik hedefleri oluşturmayın. Örneğin, katlanabilir cihazların farklı ekranları için farklı içerik hedefleri oluşturmayın.

Pencere boyutu değişikliklerinin yan etkisi olarak gezinirken aşağıdaki sorunlar ortaya çıkar:

  • Eski hedef (önceki pencere boyutu için), yeni hedefe gitmeden önce kısa bir süre içinde görünebilir
  • Geri döndürülebilirliği sağlamak amacıyla (örneğin, bir cihaz katlanmış ve açılmış olduğunda) her pencere boyutunda gezinme gerekir
  • Gezinme, sırt yığını patladığında durumu yok edebileceğinden hedefler arasında uygulama durumunu korumak zor olabilir.

Ayrıca, pencere boyutu değişiklikleri yapılırken uygulamanız ön planda bile olmayabilir. Uygulamanızın düzeni, ön plandaki uygulamadan daha fazla alan gerektirebilir. Ayrıca kullanıcı uygulamanıza geri döndüğünde yönü ve pencere boyutu değişmiş olabilir.

Uygulamanız için pencere boyutuna göre benzersiz içerik hedefleri gerekiyorsa 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 bir parçası olarak, tek bir gezinme hedefi, uygulama pencere boyutuna bağlı olarak alternatif düzenler içerebilir. Her düzen tüm pencereyi kaplar ancak farklı pencere boyutları için farklı düzenler sunulur.

Standart bir örnek, list-detail görünümüdür. Uygulamanız, küçük pencere boyutlarında liste ve ayrıntılar için birer içerik düzeni görüntüler. Liste ayrıntıları görünümü hedefine gidildiğinde başlangıçta yalnızca liste düzeni görüntülenir. Bir liste öğesi seçildiğinde, uygulamanız ayrıntı düzenini görüntüler ve listenin yerini alır. Geri kontrolü seçildiğinde, ayrıntının yerini alan liste düzeni görüntülenir. Ancak genişletilmiş pencere boyutları için liste ve ayrıntı düzenleri yan yana görüntülenir.

Görüntüleme sayısı

SlidingPaneLayout, büyük ekranlarda iki içerik bölmesini yan yana gösteren, ancak telefonlar gibi küçük ekranlı cihazlarda aynı anda yalnızca bir bölme gösteren 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ıları düzeni uygulama ile ilgili ayrıntılar için İki bölmeli düzen oluşturma bölümüne bakın.

Oluştur

Compose'da, her boyut sınıfı için uygun composable'ı yayınlamak üzere pencere boyutu sınıflarını kullanan tek bir yönlendirme, alternatif composable'ları birleştirerek bir liste ayrıntıları görünümü uygulanabilir.

Rota, genellikle tek bir composable olan, ancak alternatif composable'lar da olabilen içerik hedefine giden gezinme yoludur. İş mantığı, alternatif composable'lardan hangisinin gösterileceğini belirler. composable, görüntülenen alternatiften bağımsız olarak uygulama penceresini doldurur.

Liste ayrıntısı görünümü, üç composable'dan 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), üç composable'dan hangisinin yayınlanacağını belirler: Genişletilmiş pencere boyutu için ListAndDetail; Liste öğesinin seçili olup olmamasına bağlı olarak en yüksek boyut için ListOfItems veya ItemDetail.

Rota, NavHost içinde yer alır. Örneğin:

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

Uygulamanızın WindowMetrics'ini inceleyerek isExpandedWindowSize bağımsız değişkenini 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,
        /*...*/
      )
    }
  }
}

Rota, composable öğe ayrıntısı uygulama penceresinin tamamını kapladığında özel bir BackHandler da 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,
        /*...*/
      )
    }
  }
}

ViewModel öğesindeki uygulama durumunu pencere boyutu sınıf bilgileriyle birleştirmek, uygun composable'ı 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 tam liste ayrıntıları 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 sağlamak için her içerik hedefi 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ıyorsanız, uygulama bir boyut sınıfından diğerine her geçiş yaptığında, diğer grafiklerde kullanıcının o anki hedefini belirlemeniz, bir arka yığın oluşturmanız ve grafikler arasında farklılık gösteren durum bilgilerini eşleştirmeniz gerekir.

İç içe gezinme ana makinesi

Uygulamanız, kendi içerik hedefleri olan bir içerik hedefi barındırıyor olabilir. Örneğin, liste ayrıntıları görünümünde öğe ayrıntısı 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 gezinmeyi uygulamak için ayrıntı bölmesi, ayrıntı bölmesinden erişilen hedefleri belirten kendi gezinme grafiğine sahip 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ş gezinme grafiğinden farklıdır, çünkü iç içe yerleştirilmiş NavHost öğesine ait gezinme grafiği ana gezinme grafiğine bağlı değildir; Yani bir grafikteki hedeflerden diğerindeki hedeflere doğrudan gidemezsiniz.

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

Korunmuş durum

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, bunlar gibi yapılandırma değişiklikleri uygulamanın etkinliklerini, parçalarını, görünüm hiyerarşisini ve composable'ları yeniden oluşturur. Kullanıcı arayüzü durumunu kaydetmenin önerilen yolu, yapılandırma değişikliklerinden sonra da kalmaya devam eden bir ViewModel veya rememberSaveable kullanmaktır. (Kullanıcı arayüzü durumlarını kaydetme ve Durum ve Jetpack Compose konularına 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ı içerik parçalarını farklı pencere boyutlarında gösterebilir. Bu nedenle, geçerli pencere boyutu için geçerli olmasa bile duyarlı düzenlerin genellikle içerikle ilgili ek durum kaydetmesi gerekir. Örneğin, bir düzende yalnızca daha büyük pencere genişliklerinde ek kaydırma widget'ını gösterecek bir alan olabilir. Bir yeniden boyutlandırma etkinliği pencere genişliğinin çok küçük olmasına neden olursa widget gizlenir. Uygulama önceki boyutlarına yeniden boyutlandırıldığında, kaydırma widget'ı tekrar görünür hale gelir ve orijinal kaydırma konumu geri yüklenmelidir.

ViewModel kapsamları

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

ViewModel her zaman bir yaşam döngüsü kapsamındadır ve bu yaşam döngüsü kalıcı olarak sona erdiğinde ViewModel temizlenir ve silinebilir. ViewModel öğesinin kapsama dahil olduğu yaşam döngüsü ve dolayısıyla ViewModel öğesinin ne kadar geniş kapsamlı olarak paylaşılabileceği, ViewModel öğesini elde etmek için yetki verilen mülk türüne bağlıdır.

En basit şekilde ifade etmek gerekirse her gezinme hedefi, tamamen yalıtılmış bir kullanıcı arayüzü durumuna sahip tek bir parçadır; Dolayısıyla her parça, viewModels() özelliği tarafından yetki verilen bu parçanın kapsamında bir ViewModel elde etmek için kullanılabilir.

Parçalar arasında kullanıcı arayüzü durumunu paylaşmak için parçalarda activityViewModels() yöntemini çağırarak ViewModel kapsamının kapsamını belirleyin (etkinliğin eşdeğeri yalnızca viewModels()). Bu işlem, etkinliğin ve ona eklenen tüm parçaların ViewModel örneğini paylaşmasına izin verir. Ancak tek etkinlikli bir mimaride bu ViewModel kapsamı, uygulama olduğu sürece etkin kalır. Böylece ViewModel, herhangi bir parça kullanmasa bile bellekte kalır.

Gezinme grafiğinizde bir ödeme akışını temsil eden parça hedeflerden oluşan bir dizi olduğunu ve tüm ödeme deneyimi için mevcut durumun parçalar arasında paylaşılan bir ViewModel içinde olduğunu varsayalım. Etkinliğin ViewModel kapsamı, yalnızca çok geniş kapsamlı olmakla kalmaz, aynı zamanda başka bir sorunu da ortaya çıkarır: Kullanıcı, bir sipariş için ödeme akışından geçer ve ardından devam ederse ikinci sipariş için tekrar sipariş etse de, her iki siparişte de aynı ödeme ViewModel. İkinci sipariş ödemesinden önce, ilk siparişe ait verileri manuel olarak temizlemeniz gerekir. Böyle bir durumda yapılacak herhangi bir hata kullanıcı açısından maliyetli olabilir.

Bunun yerine, ViewModel öğesinin kapsamını mevcut NavController içindeki bir gezinme grafiğine ayarlayın. Ödeme akışının parçası olan hedefleri özetlemek için iç içe yerleştirilmiş bir gezinme grafiği oluşturun. Ardından, bu parça hedeflerinin her birinde navGraphViewModels() mülk yetkilendirmesini kullanın ve paylaşılan ViewModel öğesini elde etmek için gezinme grafiğinin kimliğini iletin. Bu sayede, kullanıcı ödeme akışından çıktığında ve iç içe yerleştirilmiş gezinme grafiği kapsam dışında kalırsa ilgili ViewModel örneği silinir ve bir sonraki ödeme için kullanılmaz.

Kapsam Mülke erişim yetkisi ViewModel öğesini şu kullanıcılarla paylaşabilir:
Parça Fragment.viewModels() Yalnızca geçerli parça
Etkinlik Activity.viewModels()

Fragment.activityViewModels()

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

İç içe yerleştirilmiş gezinme ana makinesi kullanıyorsanız (yukarıya bakın), navGraphViewModels() kullanılırken grafikler bağlı olmadığından söz konusu ana makinedeki hedefler, ana makine dışındaki hedeflerle ViewModel öğelerini paylaşamaz. Bu durumda, bunun yerine etkinliğin kapsamını kullanabilirsiniz.

Kaldırılmış durumu

Oluşturma'da, durum kaldırma ile pencere boyutu değişiklikleri sırasında durumu koruyabilirsiniz. composable'ların durumunu kompozisyon ağacında daha yüksek bir konuma kaldırarak, composable'lar artık görünür olmadığında bile durum korunabilir.

Yukarıdaki Alternatif düzenlere sahip içerik hedefleri'nin Oluştur bölümünde, liste ayrıntısı görünümü composable'larının durumunu ListDetailRoute'ye yükselttik. Böylece, hangi composable'ın görüntülendiğinden bağımsız olarak bu durum korunuyor:

@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

ziyaret edin. ziyaret edin.