Navigation 3, Sahneler aracılığıyla uygulamanızın kullanıcı arayüzü akışını 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 bölmeli deneyimleri sorunsuz bir şekilde yönetmenize olanak tanır.
Sahneleri anlama
Navigation 3'te Scene, bir veya daha fazla NavEntry örneğini oluşturan temel birimdir. Scene, kullanıcı arayüzünüzün ayrı bir görsel durumu veya bölümü olarak düşünülebilir. Bu bölüm, eski yığınınızdaki içerikleri barındırabilir ve bunların görüntülenmesini yönetebilir.
Her Scene örneği, key ve Scene sınıfı tarafından 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 birleştirildiğinde özellikle animasyon amaçları için ayırt edicilik sağlar.entries: List<NavEntry<T>>: Bu,Scenetarafından gösterilmesi gerekenNavEntrynesnelerinin listesidir. Önemli bir nokta: AynıNavEntry, geçiş sırasında birden fazlaScenesiçinde gösteriliyorsa (ör. paylaşılan öğe geçişinde) içeriği yalnızca onu gösteren en son hedefScenetarafından oluşturulur.previousEntries: List<NavEntry<T>>: Bu özellik, mevcutSceneöğesinden "geri" işlemi gerçekleştiğinde ortaya çıkacakNavEntryöğelerini tanımlar. Tahmin edilen geri gitme durumunun doğru şekilde hesaplanması için gereklidir.NavDisplay, doğru önceki durumu tahmin edip bu duruma geçebilir. Bu durum, farklı bir sınıfa, anahtara veya her ikisine sahip bir sahne olabilir.content: @Composable () -> Unit:Scene'ninentries'sini ve buScene'ye özgü çevreleyen kullanıcı arayüzü öğelerini nasıl oluşturduğunu tanımladığınız composable işlevdir.metadata: Map<String, Any>:NavDisplaygibi diğer kitaplık bileşenlerine sahneye özel bilgiler sağlar. Varsayılan olarak,entriesiçindeki sonNavEntryiçinmetadatadeğerini döndürür.
Özel sahnelerde equals ve hashCode'u uygulama
Özel Scene uygulamaları, aşağıdakileri desteklemek için equals ve hashCode öğelerini doğru şekilde uygulamalıdır:
- Sahne durumu geçişleri: Sahne stratejileri listesi tarafından bir sahne her hesaplandığında
NavDisplay, yeni sahne ile önceki sahnenin eşitliğini kontrol eder. Bir değişiklik algılarsa sahneler arasında geçiş yapmak için kullanılanSeekableTransitiondeğerini günceller. Bu da sahnelerin yaşam döngüsü durumunu etkiler. - Yer paylaşımı yönetimi:
OverlaySceneiçin (ör. iletişim kutuları),NavDisplay, yer paylaşımı yaşam döngülerini ve çıkış animasyonlarını izlemek için sahne nesnesinin kendisinikeyolarak kullanır.
Sahnenin içeriğini tanımlayan key, entries ve previousEntries gibi tüm özelliklerin equals ve hashCode uygulamalarına dahil edildiğini doğrulayın. Genel olarak, bu yöntemlerin uygulanmasına geri çağırmaları (ör. onBack) dahil etmekten kaçının. Çünkü bunlar, mantıksal sahne durumunu değiştirmeden örnekleri değiştirebilir.
Sahne stratejilerini anlama
SceneStrategy, eski yığındaki belirli bir NavEntry listesinin nasıl düzenlenmesi ve Scene öğesine nasıl geçirilmesi gerektiğini belirleyen mekanizmadır. Temel olarak, mevcut eski yığın girişleri sunulduğunda SceneStrategy, kendisine iki önemli soru sorar:
- Bu girişlerden bir
Sceneoluşturabilir miyim?SceneStrategy, verilenNavEntryöğelerini işleyebileceğini ve anlamlı birScene(ör. iletişim kutusu veya çok bölmeli düzen) oluşturabileceğini belirlerse devam eder. Aksi takdirdenulldöndürerek diğer stratejilereSceneoluşturma şansı verir. - Öyleyse bu girişleri
Scene?içinde nasıl düzenlemeliyim? BirSceneStrategy, girişleri işlemeyi taahhüt ettikten sonraSceneoluşturma ve belirtilenNavEntryöğelerinin buSceneiçinde nasıl gösterileceğini tanımlama sorumluluğunu üstlenir.
SceneStrategy sınıfının temelinde calculateScene yöntemi yer alır:
@Composable public fun calculateScene( entries: List<NavEntry<T>>, onBack: (count: Int) -> Unit, ): Scene<T>?
Bu yöntem, eski yığından mevcut List<NavEntry<T>> değerini alan bir SceneStrategyScope 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 için gerekli olabilecek isteğe bağlı bağımsız değişkenleri (ör. onBack geri çağırma) korumaktan sorumludur.
Sahneler ve sahne stratejilerinin birlikte çalışma şekli
NavDisplay, eski yığınınızı gözlemleyen ve uygun Scene öğesini belirleyip oluşturmak için bir veya daha fazla SceneStrategy kullanan merkezi bir composable'dır.
NavDisplay öğesinin sceneStrategies parametresi, gösterilecek Scene değerini hesaplamaktan sorumlu SceneStrategy örneklerinin listesini bekler. Sağlanan stratejiler tarafından Scene hesaplanmazsa NavDisplay, varsayılan olarak otomatik şekilde SinglePaneSceneStrategy kullanmaya geri döner.
Etkileşimin dökümü şu şekildedir:
- Eski yığınınıza anahtar eklediğinizde veya kaldırdığınızda (ör.
backStack.add()veyabackStack.removeLastOrNull()kullanarak)NavDisplaybu değişiklikleri gözlemler. NavDisplay, geçerliNavEntrylistesini (eski yığın anahtarlarından türetilir) yapılandırılmışsceneStrategies'ye sırayla iletir veScenedöndürülene kadar her birindecalculateScene'yi çağırır.- Bir
SceneStrategybaşarıyla birScenedöndürdüğündeNavDisplay, buScene'nincontentöğesini oluşturur.NavDisplay,Sceneözelliklerine göre animasyonları ve tahmin edilen geri gitme özelliğini de yönetir.
Örnek: Tek bölmeli düzen (varsayılan davranış)
Oluşturabileceğiniz en basit özel düzen, tek bölmeli 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 bir liste-ayrıntı düzeninin nasıl oluşturulacağı gösterilmektedir:
- Pencere genişliği, iki bölmeyi destekleyecek kadar geniştir (en az
WIDTH_DP_MEDIUM_LOWER_BOUND). - Eski yığın, belirli meta verileri kullanarak liste-ayrıntı düzeninde gösterilme desteğini beyan eden girişler içerir.
Aşağıdaki snippet, ListDetailScene.kt için kaynak kodudur ve hem ListDetailScene hem de ListDetailSceneStrategy öğelerini içerir:
// --- 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) } } }
Bu ListDetailSceneStrategy öğesini NavDisplay uygulamanızda 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() meta verilerini içerecek şekilde değiştirin. Ardından, tek bölmeli senaryolar için varsayılan yedek seçeneği 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() }, 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) }
Kendi liste ayrıntısı sahnenizi oluşturmak istemiyorsanız bir sonraki bölümde gösterildiği gibi, mantıklı ayrıntılar ve yer tutucu desteği içeren Material liste ayrıntısı sahnesini kullanabilirsiniz.
Liste-ayrıntı içeriğini Materyal Uyarlanabilir Sahne'de görüntüleme
liste-ayrıntı düzeni kullanım alanı için androidx.compose.material3.adaptive:adaptive-navigation3 yapısı, liste-ayrıntı düzeni Scene oluşturan bir ListDetailSceneStrategy sağlar. Bu Scene, karmaşık çok bölmeli düzenleri (liste, ayrıntı ve ekstra bölmeler) otomatik olarak işler ve pencere boyutuna ve cihaz durumuna göre uyarlar.
Materyal liste-ayrıntı düzeni 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-navigation3ekleyin. - Girişlerinizi
ListDetailSceneStrategymeta verileriyle tanımlayın:NavEntrysöğenizi uygun panel görünümü için işaretlemek üzerelistPane(), detailPane()veextraPane()kullanın.listPane()yardımcısı, hiçbir öğe seçilmediğinde birdetailPlaceholderbelirtmenize de olanak tanır. rememberListDetailSceneStrategy() kullanımı: Bu composable işlev,NavDisplaytarafından kullanılabilecek önceden yapılandırılmış birListDetailSceneStrategysağlar.
Aşağıdaki snippet, ListDetailSceneStrategy kullanımını gösteren örnek bir Activity'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() }, 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") } } ) } } } }