Navigation 3, Scenes के ज़रिए आपके ऐप्लिकेशन के यूज़र इंटरफ़ेस (यूआई) फ़्लो को मैनेज करने के लिए, एक बेहतरीन और फ़्लेक्सिबल सिस्टम उपलब्ध कराता है. सीन की मदद से, अपनी पसंद के मुताबिक लेआउट बनाए जा सकते हैं. साथ ही, इन्हें अलग-अलग स्क्रीन साइज़ के हिसाब से अडजस्ट किया जा सकता है. इसके अलावा, मल्टी-पैन के जटिल अनुभव को आसानी से मैनेज किया जा सकता है.
सीन के बारे में जानकारी
Navigation 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>>: इस प्रॉपर्टी से उनNavEntrys के बारे में पता चलता है जो मौजूदा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>?
यह तरीका, SceneStrategyScope पर एक एक्सटेंशन फ़ंक्शन है. यह पिछली गतिविधियों से List<NavEntry<T>> लेता है. अगर दी गई एंट्री से कोई वैल्यू जनरेट की जा सकती है, तो इसे Scene<T> वैल्यू दिखानी चाहिए. अगर ऐसा नहीं किया जा सकता, तो इसे null वैल्यू दिखानी चाहिए.
SceneStrategyScope, SceneStrategy के लिए ज़रूरी किसी भी वैकल्पिक तर्क को बनाए रखने के लिए ज़िम्मेदार होता है. जैसे, onBack कॉलबैक.
SceneStrategy में then इनफ़िक्स फ़ंक्शन भी उपलब्ध है. इसकी मदद से, कई रणनीतियों को एक साथ इस्तेमाल किया जा सकता है. इससे फ़ैसले लेने की एक फ़्लेक्सिबल पाइपलाइन बनती है. इसमें हर रणनीति, Scene का हिसाब लगाने की कोशिश कर सकती है. अगर ऐसा नहीं हो पाता है, तो यह चेन में मौजूद अगली रणनीति को काम सौंप देती है.
सीन और सीन की रणनीतियां एक साथ कैसे काम करती हैं
NavDisplay एक सेंट्रल कंपोज़ेबल है. यह आपके बैक स्टैक पर नज़र रखता है और SceneStrategy का इस्तेमाल करके, सही Scene का पता लगाता है और उसे रेंडर करता है.
NavDisplay's sceneStrategy पैरामीटर को SceneStrategy की ज़रूरत होती है. यह SceneStrategy, Scene को दिखाने के लिए कैलकुलेट करता है. अगर दी गई रणनीति (या रणनीतियों की चेन) से कोई Scene नहीं मिलता है, तो NavDisplay डिफ़ॉल्ट रूप से SinglePaneSceneStrategy का इस्तेमाल करने लगता है.
यहां इंटरैक्शन के बारे में बताया गया है:
- जब बैक स्टैक में कुंजियां जोड़ी या हटाई जाती हैं (जैसे,
backStack.add()याbackStack.removeLastOrNull()का इस्तेमाल करके), तोNavDisplayइन बदलावों को ट्रैक करता है. NavDisplay, कॉन्फ़िगर किए गएSceneStrategy's calculateSceneतरीके कोNavEntrysकी मौजूदा सूची (बैकस्टैक कुंजियों से मिली) पास करता है.- अगर
SceneStrategy,Sceneको सही तरीके से वापस भेजता है, तोNavDisplayउसSceneकेcontentको रेंडर करता है.NavDisplay,Sceneकी प्रॉपर्टी के आधार पर ऐनिमेशन और अनुमानित बैक बटन की सुविधा को भी मैनेज करता है.
उदाहरण: सिंगल पैन लेआउट (डिफ़ॉल्ट व्यवहार)
सबसे आसान कस्टम लेआउट, सिंगल-पैन डिसप्ले होता है. अगर कोई अन्य SceneStrategy पहले से मौजूद नहीं है, तो यह डिफ़ॉल्ट रूप से काम करता है.
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) ) }
उदाहरण: सूची-जानकारी वाला बुनियादी लेआउट (कस्टम सीन और रणनीति)
इस उदाहरण में, सामान्य सूची-जानकारी वाला लेआउट बनाने का तरीका बताया गया है. यह लेआउट इन दो शर्तों के आधार पर चालू होता है:
- विंडो की चौड़ाई इतनी होनी चाहिए कि दो पैनल दिख सकें. इसका मतलब है कि विंडो की चौड़ाई कम से कम
WIDTH_DP_MEDIUM_LOWER_BOUNDहोनी चाहिए. - बैक स्टैक में ऐसी एंट्री होती हैं जिन्होंने खास मेटाडेटा का इस्तेमाल करके, सूची-जानकारी वाले लेआउट में दिखाए जाने की सुविधा के लिए सहमति दी है.
यहां दिया गया स्निपेट, ListDetailScene.kt का सोर्स कोड है. इसमें ListDetailScene और ListDetailSceneStrategy, दोनों शामिल हैं:
// --- 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) } }
इस ListDetailSceneStrategy का इस्तेमाल अपने NavDisplay में करने के लिए, अपने entryProvider कॉल में बदलाव करें. इसमें ListDetailScene.listPane() मेटाडेटा को शामिल करें. यह मेटाडेटा उस एंट्री के लिए होना चाहिए जिसे आपको सूची लेआउट के तौर पर दिखाना है. साथ ही, ListDetailScene.detailPane() मेटाडेटा उस एंट्री के लिए होना चाहिए जिसे आपको जानकारी लेआउट के तौर पर दिखाना है. इसके बाद, sceneStrategy के तौर पर ListDetailSceneStrategy() दें. इससे सिंगल-पैन वाले मामलों के लिए, डिफ़ॉल्ट फ़ॉलबैक पर भरोसा किया जा सकेगा:
// 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) }
अगर आपको खुद का लिस्ट-डिटेल सीन नहीं बनाना है, तो Material लिस्ट-डिटेल सीन का इस्तेमाल किया जा सकता है. इसमें काम की जानकारी और प्लेसहोल्डर के लिए सहायता मिलती है. इसके बारे में अगले सेक्शन में बताया गया है.
मटेरियल अडैप्टिव सीन में सूची और जानकारी वाला कॉन्टेंट दिखाना
सूची-जानकारी के इस्तेमाल के उदाहरण के लिए, androidx.compose.material3.adaptive:adaptive-navigation3 आर्टफ़ैक्ट एक ListDetailSceneStrategy उपलब्ध कराता है, जो सूची-जानकारी वाला Scene बनाता है. यह Scene, एक से ज़्यादा पैन वाले जटिल लेआउट (सूची, जानकारी, और अतिरिक्त पैन) को अपने-आप मैनेज करता है. साथ ही, विंडो के साइज़ और डिवाइस की स्थिति के हिसाब से उन्हें अडजस्ट करता है.
मटेरियल लिस्ट-डिटेल Scene बनाने के लिए, यह तरीका अपनाएं:
- डिपेंडेंसी जोड़ना: अपने प्रोजेक्ट की
build.gradle.ktsफ़ाइल मेंandroidx.compose.material3.adaptive:adaptive-navigation3शामिल करें. ListDetailSceneStrategyमेटाडेटा की मदद से अपनी एंट्री तय करें:listPane(), detailPane()औरextraPane()का इस्तेमाल करके,NavEntrysको मार्क करें, ताकि उसे सही पैनल में दिखाया जा सके.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<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") } } ) } } } }