नेविगेशन 3 में, सीन की मदद से ऐप्लिकेशन के यूज़र इंटरफ़ेस (यूआई) फ़्लो को मैनेज करने के लिए, एक बेहतर और सुविधाजनक सिस्टम उपलब्ध कराया गया है. सीन की मदद से, अपनी ज़रूरत के मुताबिक लेआउट बनाए जा सकते हैं. साथ ही, इन्हें अलग-अलग स्क्रीन साइज़ के हिसाब से अडजस्ट किया जा सकता है. इसके अलावा, कई पैनल वाले कॉम्प्लेक्स अनुभवों को आसानी से मैनेज किया जा सकता है.
सीन के बारे में जानकारी
नेविगेशन 3 में, Scene
एक बुनियादी यूनिट है, जो एक या उससे ज़्यादा NavEntry
इंस्टेंस को रेंडर करती है. Scene
को अपने यूज़र इंटरफ़ेस (यूआई) के अलग-अलग विज़ुअल स्टेटस या सेक्शन के तौर पर देखें. इसमें आपके बैकस्टैक से कॉन्टेंट को दिखाया और मैनेज किया जा सकता है.
हर Scene
इंस्टेंस की पहचान, उसके key
और Scene
की क्लास से की जाती है. यह यूनीक आइडेंटिफ़ायर ज़रूरी है, क्योंकि Scene
में बदलाव होने पर यह टॉप-लेवल ऐनिमेशन को चलाता है.
Scene
इंटरफ़ेस में ये प्रॉपर्टी होती हैं:
key: Any
: इस खासScene
इंस्टेंस के लिए यूनीक आइडेंटिफ़ायर.Scene
की क्लास के साथ मिलकर, यह बटन मुख्य रूप से ऐनिमेशन के मकसद से अलग दिखता है.entries: List<NavEntry<T>>
: यहNavEntry
ऑब्जेक्ट की सूची है, जिन्हेंScene
दिखाता है. अहम बात यह है कि अगर किसी ट्रांज़िशन (उदाहरण के लिए, शेयर किए गए एलिमेंट के ट्रांज़िशन) के दौरान एक हीNavEntry
कईScenes
में दिखता है, तो उसका कॉन्टेंट सिर्फ़ उस सबसे नए टारगेटScene
से रेंडर किया जाएगा जो उसे दिखा रहा है.previousEntries: List<NavEntry<T>>
: यह प्रॉपर्टी उनNavEntry
को तय करती है जो मौजूदाScene
से "बैक" कार्रवाई होने पर दिखेंगे. यह सही अनुमानित बैक स्टेटस का हिसाब लगाने के लिए ज़रूरी है. इससेNavDisplay
को सही पिछली स्थिति का अनुमान लगाने और उसमें ट्रांज़िशन करने में मदद मिलती है. यह स्थिति, किसी दूसरी क्लास और/या बटन वाला सीन हो सकता है.content: @Composable () -> Unit
: यह एक कंपोजेबल फ़ंक्शन है, जिसमें यह तय किया जाता है किScene
अपनेentries
और उससे जुड़े यूज़र इंटरफ़ेस (यूआई) के एलिमेंट को कैसे रेंडर करता है.Scene
सीन की रणनीतियों को समझना
SceneStrategy
एक ऐसा तरीका है जिससे यह तय होता है कि बैक स्टैक में मौजूद NavEntry
की किसी सूची को कैसे व्यवस्थित किया जाए और Scene
में कैसे बदला जाए. असल में, मौजूदा बैक स्टैक एंट्री के साथ दिखाए जाने पर, SceneStrategy
खुद से दो मुख्य सवाल पूछता है:
- क्या इन एंट्री से
Scene
बनाया जा सकता है? अगरSceneStrategy
यह तय करता है कि वह दिए गएNavEntry
को हैंडल कर सकता है और कोई काम काScene
(जैसे, डायलॉग या मल्टी-पैन लेआउट) बना सकता है, तो वह आगे बढ़ता है. अगर ऐसा नहीं होता है, तो यहnull
दिखाता है. इससे अन्य रणनीतियों कोScene
बनाने का मौका मिलता है. - अगर ऐसा है, तो मुझे उन एंट्री को
Scene?
में कैसे व्यवस्थित करना चाहिए जब कोईSceneStrategy
, एंट्री को मैनेज करने के लिए सहमत होता है, तो वहScene
बनाने की ज़िम्मेदारी लेता है. साथ ही, यह तय करता है कि तय किए गएNavEntry
, उसScene
में कैसे दिखेंगे.
SceneStrategy
का मुख्य हिस्सा उसका calculateScene
तरीका है:
@Composable public fun calculateScene( entries: List<NavEntry<T>>, onBack: (count: Int) -> Unit, ): Scene<T>?
यह तरीका, बैक स्टैक से मौजूदा List<NavEntry<T>>
और onBack
कॉलबैक फ़ंक्शन लेता है. अगर यह दी गई एंट्री से कोई Scene<T>
बना पाता है, तो यह वैल्यू दिखाएगा. अगर नहीं बना पाता है, तो null
दिखाएगा.
SceneStrategy
में then
इनफ़िक्स फ़ंक्शन भी उपलब्ध है. इसकी मदद से, एक साथ कई रणनीतियों को जोड़ा जा सकता है. इससे, फ़ैसले लेने के लिए एक ऐसी प्लैटफ़ॉर्म बनती है जिसमें हर रणनीति, Scene
का हिसाब लगाने की कोशिश कर सकती है. अगर वह ऐसा नहीं कर पाती है, तो वह चेन में मौजूद अगली रणनीति को यह काम सौंप देती है.
सीन और सीन की रणनीतियां एक साथ कैसे काम करती हैं
NavDisplay
एक मुख्य कॉम्पोज़ेबल है, जो आपके बैक स्टैक को मॉनिटर करता है. साथ ही, सही Scene
तय करने और उसे रेंडर करने के लिए, SceneStrategy
का इस्तेमाल करता है.
NavDisplay's sceneStrategy
पैरामीटर के लिए, SceneStrategy
की ज़रूरत होती है, जो दिखाए जाने वाले Scene
का हिसाब लगाने के लिए ज़िम्मेदार होता है. अगर दी गई रणनीति (या रणनीतियों की चेन) से कोई Scene
कैलकुलेट नहीं किया जाता है, तो NavDisplay
डिफ़ॉल्ट रूप से SinglePaneSceneStrategy
का इस्तेमाल करने लगता है.
यहां इंटरैक्शन के बारे में जानकारी दी गई है:
- जब बैक स्टैक में बटन जोड़े जाते हैं या हटाए जाते हैं (उदाहरण के लिए,
backStack.add()
याbackStack.removeLastOrNull()
का इस्तेमाल करके), तोNavDisplay
इन बदलावों को देखता है. NavDisplay
,NavEntrys
की मौजूदा सूची (बैकस्टैक की से मिली) को कॉन्फ़िगर किए गएSceneStrategy's calculateScene
तरीके पर भेजता है.- अगर
SceneStrategy
,Scene
को सही तरीके से दिखाता है, तोNavDisplay
उसScene
काcontent
रेंडर करता है.NavDisplay
,Scene
की प्रॉपर्टी के आधार पर ऐनिमेशन और अनुमानित बैक भी मैनेज करता है.
उदाहरण: एक पैनल वाला लेआउट (डिफ़ॉल्ट रूप से)
सबसे आसान कस्टम लेआउट, सिंगल-पैन डिसप्ले है. अगर कोई दूसरा SceneStrategy
प्राथमिकता नहीं लेता है, तो यह डिफ़ॉल्ट तौर पर दिखता है.
data class SinglePaneScene<T : Any>( override val key: T, 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.invoke(entry.key) } } /** * A [SceneStrategy] that always creates a 1-entry [Scene] simply displaying the last entry in the * list. */ public class SinglePaneSceneStrategy<T : Any> : SceneStrategy<T> { @Composable override fun calculateScene(entries: List<NavEntry<T>>, onBack: (Int) -> Unit): Scene<T> = SinglePaneScene( key = entries.last().key, entry = entries.last(), previousEntries = entries.dropLast(1) ) }
उदाहरण: दो पैनल वाला बुनियादी लेआउट (कस्टम सीन और रणनीति)
इस उदाहरण में, दो पैनल वाला ऐसा आसान लेआउट बनाने का तरीका बताया गया है जो दो शर्तों के आधार पर चालू होता है:
- विंडो की चौड़ाई, दो पैनल के लिए ज़रूरत के मुताबिक होनी चाहिए. जैसे, कम से कम
WIDTH_DP_MEDIUM_LOWER_BOUND
. - बैक स्टैक में मौजूद सबसे ऊपर की दो एंट्री, खास मेटाडेटा का इस्तेमाल करके, दो पैनल वाले लेआउट में दिखने के लिए साफ़ तौर पर बताती हैं कि वे इस सुविधा के साथ काम करती हैं.
यहां दिया गया स्निपेट, TwoPaneScene.kt
और
TwoPaneSceneStrategy.kt
के लिए मिला-जुला सोर्स कोड है:
// --- TwoPaneScene --- /** * A custom [Scene] that displays two [NavEntry]s side-by-side in a 50/50 split. */ class TwoPaneScene<T : Any>( override val key: Any, override val previousEntries: List<NavEntry<T>>, val firstEntry: NavEntry<T>, val secondEntry: NavEntry<T> ) : Scene<T> { override val entries: List<NavEntry<T>> = listOf(firstEntry, secondEntry) override val content: @Composable (() -> Unit) = { Row(modifier = Modifier.fillMaxSize()) { Column(modifier = Modifier.weight(0.5f)) { firstEntry.content.invoke(firstEntry.key) } Column(modifier = Modifier.weight(0.5f)) { secondEntry.content.invoke(secondEntry.key) } } } companion object { internal const val TWO_PANE_KEY = "TwoPane" /** * Helper function to add metadata to a [NavEntry] indicating it can be displayed * in a two-pane layout. */ fun twoPane() = mapOf(TWO_PANE_KEY to true) } } // --- TwoPaneSceneStrategy --- /** * A [SceneStrategy] that activates a [TwoPaneScene] if the window is wide enough * and the top two back stack entries declare support for two-pane display. */ class TwoPaneSceneStrategy<T : Any> : SceneStrategy<T> { @OptIn(ExperimentalMaterial3AdaptiveApi::class, ExperimentalMaterial3WindowSizeClassApi::class) @Composable override fun calculateScene( entries: List<NavEntry<T>>, onBack: (Int) -> Unit ): Scene<T>? { val windowSizeClass = currentWindowAdaptiveInfo().windowSizeClass // Condition 1: Only return a Scene if the window is sufficiently wide to render two panes. // We use isWidthAtLeastBreakpoint with WIDTH_DP_MEDIUM_LOWER_BOUND (600dp). if (!windowSizeClass.isWidthAtLeastBreakpoint(WIDTH_DP_MEDIUM_LOWER_BOUND)) { return null } val lastTwoEntries = entries.takeLast(2) // Condition 2: Only return a Scene if there are two entries, and both have declared // they can be displayed in a two pane scene. return if (lastTwoEntries.size == 2 && lastTwoEntries.all { it.metadata.containsKey(TwoPaneScene.TWO_PANE_KEY) } ) { val firstEntry = lastTwoEntries.first() val secondEntry = lastTwoEntries.last() // The scene key must uniquely represent the state of the scene. val sceneKey = Pair(firstEntry.key, secondEntry.key) TwoPaneScene( key = sceneKey, // Where we go back to is a UX decision. In this case, we only remove the top // entry from the back stack, despite displaying two entries in this scene. // This is because in this app we only ever add one entry to the // back stack at a time. It would therefore be confusing to the user to add one // when navigating forward, but remove two when navigating back. previousEntries = entries.dropLast(1), firstEntry = firstEntry, secondEntry = secondEntry ) } else { null } } }
अपने NavDisplay
में इस TwoPaneSceneStrategy
का इस्तेमाल करने के लिए, अपने entryProvider
कॉल में बदलाव करें. ऐसा करके, उन एंट्री के लिए TwoPaneScene.twoPane()
मेटाडेटा शामिल करें जिन्हें आपको दो पैनल वाले लेआउट में दिखाना है. इसके बाद, एक पैनल वाले मामलों के लिए डिफ़ॉल्ट फ़ॉलबैक पर भरोसा करते हुए, sceneStrategy
के तौर पर TwoPaneSceneStrategy()
दें:
// Define your navigation keys @Serializable data object ProductList : NavKey @Serializable data class ProductDetail(val id: String) : NavKey @Composable fun MyAppContent() { val backStack = rememberNavBackStack(ProductList) NavDisplay( backStack = backStack, entryProvider = entryProvider { entry<ProductList>( // Mark this entry as eligible for two-pane display metadata = TwoPaneScene.twoPane() ) { key -> Column { Text("Product List") Button(onClick = { backStack.add(ProductDetail("ABC")) }) { Text("View Details for ABC (Two-Pane Eligible)") } } } entry<ProductDetail>( // Mark this entry as eligible for two-pane display metadata = TwoPaneScene.twoPane() ) { key -> Text("Product Detail: ${key.id} (Two-Pane Eligible)") } // ... other entries ... }, // Simply provide your custom strategy. NavDisplay will fall back to SinglePaneSceneStrategy automatically. sceneStrategy = TwoPaneSceneStrategy<Any>(), onBack = { count -> repeat(count) { if (backStack.isNotEmpty()) { backStack.removeLastOrNull() } } } ) }
मटीरियल अडैप्टिव सीन में, सूची की ज़्यादा जानकारी वाला कॉन्टेंट दिखाना
list-detail के इस्तेमाल के उदाहरण के लिए, androidx.compose.material3.adaptive:adaptive-navigation3
आर्टफ़ैक्ट एक ListDetailSceneStrategy
उपलब्ध कराता है, जो list-detail Scene
बनाता है. यह Scene
कई पैनल के जटिल लेआउट (सूची, ज़्यादा जानकारी, और अतिरिक्त पैनल) को अपने-आप मैनेज करता है. साथ ही, विंडो के साइज़ और डिवाइस की स्थिति के हिसाब से उन्हें अडजस्ट करता है.
मटीरियल की लिस्ट की जानकारी Scene
बनाने के लिए, यह तरीका अपनाएं:
- डिपेंडेंसी जोड़ना: अपने प्रोजेक्ट की
build.gradle.kts
फ़ाइल मेंandroidx.compose.material3.adaptive:adaptive-navigation3
शामिल करें. ListDetailSceneStrategy
मेटाडेटा की मदद से अपनी एंट्री तय करना: सही पैनल डिसप्ले के लिए,NavEntrys
को मार्क करने के लिएlistPane(), detailPane()
औरextraPane()
का इस्तेमाल करें.listPane()
हेल्पर की मदद से, कोई आइटम न चुनने पर भीdetailPlaceholder
चुना जा सकता है.rememberListDetailSceneStrategy
का इस्तेमाल करें: यह कंपोजेबल फ़ंक्शन, पहले से कॉन्फ़िगर किया गयाListDetailSceneStrategy
उपलब्ध कराता है. इसका इस्तेमालNavDisplay
कर सकता है.
यहां दिया गया स्निपेट, Activity
का एक सैंपल है. इसमें ListDetailSceneStrategy
के इस्तेमाल के बारे में बताया गया है:
@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<Any>() NavDisplay( backStack = backStack, modifier = Modifier.padding(paddingValues), onBack = { keysToRemove -> repeat(keysToRemove) { 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") } } ) } } } }