Navigation 3, uygulamanızın kullanıcı arayüzü akışını Sahneler aracılığıyla yönetmek için güçlü ve esnek bir sistem sunar. Sahneler, son derece özelleştirilmiş düzenler oluşturmanıza, farklı ekran boyutlarına uyum sağlamanıza ve karmaşık çok panelli deneyimleri sorunsuz bir şekilde yönetmenize olanak tanır.
Sahneleri Anlama
Navigation 3'te Scene, bir veya daha fazla NavEntry örneğini oluşturmak için kullanılan temel birimdir. Scene, kullanıcı arayüzünüzün arka yığınınızdaki içeriklerin gösterimini içerebilen ve yönetebilen ayrı bir görsel durumu veya bölümü olarak düşünülebilir.
Her Scene örneği, key ve Scene sınıfı ile benzersiz şekilde tanımlanır. Bu benzersiz tanımlayıcı, Scene değiştiğinde üst düzey animasyonu yönlendirdiği için çok önemlidir.
Scene arayüzü aşağıdaki özelliklere sahiptir:
key: Any: Bu belirliSceneörneği için benzersiz bir tanımlayıcı. Bu anahtar,Scenesınıfıyla birlikte özellikle animasyon amacıyla ayırt ediciliği sağlar.entries: List<NavEntry<T>>: Bu,Sceneöğesinin görüntülemekten sorumlu olduğuNavEntrynesnelerinin listesidir. Önemli olarak, aynıNavEntrybir geçiş sırasında birden fazlaScenesiçinde gösteriliyorsa (ör. paylaşılan bir öğe geçişinde) içeriği yalnızca bunu görüntüleyen en son hedefScenetarafından oluşturulur.previousEntries: List<NavEntry<T>>: Bu özellik, mevcutScenekonumundan "geri" işlemi yapılması durumunda ortaya çıkacakNavEntry'leri tanımlar. Bu, doğru tahmini geri durumunu hesaplamak için gereklidir.NavDisplay, farklı bir sınıfa ve/veya anahtara sahip bir Sahne olabilecek doğru önceki durumu tahmin edip bu duruma geçiş yapabilir.content: @Composable () -> Unit: Bu,Sceneöğesininentriesöğesini ve buSceneöğesine özgü tüm çevreleyen kullanıcı arayüzü öğelerini nasıl oluşturduğunu tanımladığınız birleştirilebilir işlevdir.
Sahne stratejilerini anlama
SceneStrategy, geri yığındaki belirli bir NavEntry listesinin nasıl düzenlenmesi ve Scene'ye nasıl geçirilmesi gerektiğini belirleyen mekanizmadır. Temel olarak, mevcut geri yığın girişleri sunulduğunda bir
SceneStrategy kendisiyle ilgili iki temel soru sorar:
- Bu girişlerden
Sceneoluşturabilir miyim?SceneStrategy,NavEntry'leri işleyebileceğini ve anlamlı birSceneoluşturabileceğini (ör. iletişim kutusu veya çok panelli düzen) belirlerse işleme devam eder. Aksi takdirde, diğer stratejilerinnulloluşturmasına olanak tanımak içinnulldeğerini döndürür.Scene - Bu durumda, bu girişleri
Scene?BirSceneStrategy, girişleri işlemeyi kabul ettiğindeSceneoluşturma ve belirtilenNavEntry'ların buSceneiçinde nasıl görüntüleneceğini tanımlama sorumluluğunu üstlenir.
SceneStrategy'nın temelinde calculateScene yöntemi bulunur:
@Composable public fun calculateScene( entries: List<NavEntry<T>>, onBack: (count: Int) -> Unit, ): Scene<T>?
Bu yöntem, arka yığında geçerli List<NavEntry<T>> öğesini alan bir SceneStrategyScope üzerindeki uzantı işlevidir. Sağlanan girişlerden başarılı bir şekilde oluşturabiliyorsa Scene<T>, oluşturamıyorsa null döndürmelidir.
SceneStrategyScope, SceneStrategy'nin ihtiyaç duyabileceği isteğe bağlı bağımsız değişkenleri (ör. onBack geri çağırma) korumakla sorumludur.
SceneStrategy ayrıca, birden fazla stratejiyi birbirine bağlamanıza olanak tanıyan kullanışlı bir then infix işlevi de sunar. Bu, her stratejinin Scene hesaplamaya çalışabileceği ve hesaplayamadığı durumlarda zincirdeki bir sonraki stratejiye devredeceği esnek bir karar verme hattı oluşturur.
Sahneler ve sahne stratejileri birlikte nasıl çalışır?
NavDisplay, arka yığını gözlemleyen ve uygun Scene'ı belirleyip oluşturmak için SceneStrategy kullanan merkezi birleştirilebilir öğedir.
NavDisplay's sceneStrategy parametresi, görüntülenecek Scene değerini hesaplamaktan sorumlu bir SceneStrategy bekler. Sağlanan strateji (veya strateji zinciri) tarafından Scene hesaplanmazsa NavDisplay varsayılan olarak otomatik olarak SinglePaneSceneStrategy kullanmaya geri döner.
Etkileşimin dökümü:
- Geriye gitme yığınıza anahtar eklediğinizde veya anahtar kaldırdığınızda (ör.
backStack.add()veyabackStack.removeLastOrNull()kullanarak)NavDisplaybu değişiklikleri gözlemler. NavDisplay, yapılandırılmışSceneStrategy's calculateSceneyöntemineNavEntrysöğelerinin mevcut listesini (arka yığın anahtarlarından türetilmiştir) iletir.SceneStrategy,Sceneöğesini başarıyla döndürürseNavDisplay,Sceneöğesinincontentöğesini oluşturur.NavDisplayayrıcaSceneözelliklerine göre animasyonları ve tahmini geri özelliğini de yönetir.
Örnek: Tek bölmeli düzen (varsayılan davranış)
En basit özel düzen, tek bölmeli bir ekrandır. Başka bir SceneStrategy öncelikli değilse varsayılan davranış budur.
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) ) }
Örnek: Temel liste-ayrıntı düzeni (özel sahne ve strateji)
Bu örnekte, iki koşula göre etkinleştirilen basit bir liste-ayrıntı düzeninin nasıl oluşturulacağı gösterilmektedir:
- Pencere genişliği, iki bölmeyi destekleyecek kadar geniştir (ör. en az
WIDTH_DP_MEDIUM_LOWER_BOUND). - Geri yığında, belirli meta veriler kullanılarak liste-ayrıntı düzeninde gösterilme desteği beyan edilmiş girişler bulunur.
Aşağıdaki snippet, ListDetailScene.kt için kaynak kodudur ve hem ListDetailScene hem de ListDetailSceneStrategy içerir:
// --- ListDetailScene --- /** * A [Scene] that displays a list and a detail [NavEntry] side-by-side in a 40/60 split. * */ 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.containsKey(DETAIL_KEY) } ?: return null val listEntry = entries.findLast { it.metadata.containsKey(LIST_KEY) } ?: 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 ) } companion object { internal const val LIST_KEY = "ListDetailScene-List" internal const val DETAIL_KEY = "ListDetailScene-Detail" /** * Helper function to add metadata to a [NavEntry] indicating it can be displayed * as a list in the [ListDetailScene]. */ fun listPane() = mapOf(LIST_KEY to true) /** * Helper function to add metadata to a [NavEntry] indicating it can be displayed * as a list in the [ListDetailScene]. */ fun detailPane() = mapOf(DETAIL_KEY to true) } }
Bu ListDetailSceneStrategy öğesini NavDisplay içinde kullanmak için entryProvider çağrılarınızı, liste düzeni olarak göstermek istediğiniz giriş için ListDetailScene.listPane() meta verilerini ve ayrıntı düzeni olarak göstermek istediğiniz giriş için ListDetailScene.detailPane() öğesini içerecek şekilde değiştirin. Ardından, tek pencereli senaryolarda varsayılan geri dönüşü kullanarak ListDetailSceneStrategy() değerini sceneStrategy olarak sağlayın:
// 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() }, sceneStrategy = 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) }
Kendi liste-ayrıntı sahnenizi oluşturmak istemiyorsanız sonraki bölümde gösterildiği gibi, anlamlı ayrıntılar ve yer tutucular için destek içeren Material liste-ayrıntı sahnesini kullanabilirsiniz.
Liste-ayrıntı içeriğini Material Adaptive Scene'de görüntüleme
Liste-ayrıntı kullanım alanında, androidx.compose.material3.adaptive:adaptive-navigation3 yapısı, liste-ayrıntı Scene oluşturan bir ListDetailSceneStrategy sağlar. Bu Scene
karmaşık çok panelli düzenlemeleri (liste, ayrıntı ve ek paneller) otomatik olarak yönetir ve bunları pencere boyutuna ve cihaz durumuna göre uyarlar.
Bir Material liste-ayrıntı Scene oluşturmak için aşağıdaki adımları uygulayın:
- Bağımlılığı ekleyin: Projenizin
build.gradle.ktsdosyasınaandroidx.compose.material3.adaptive:adaptive-navigation3öğesini ekleyin. - Girişlerinizi
ListDetailSceneStrategymeta verileriyle tanımlayın:NavEntrysöğenizi uygun bölme görünümü için işaretlemek üzerelistPane(), detailPane()veextraPane()kullanın.listPane()yardımcı aracı, öğe seçilmediğinde delistPane()belirtmenize olanak tanır.detailPlaceholder rememberListDetailSceneStrategy() işlevini kullanın: Bu composable işlev,NavDisplaytarafından kullanılabilen önceden yapılandırılmış birListDetailSceneStrategysağlar.
Aşağıdaki snippet, Activity kullanımını gösteren örnek bir ListDetailSceneStrategy'dir:
@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() }, sceneStrategy = 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") } } ) } } } }