सीन का इस्तेमाल करके कस्टम लेआउट बनाना

Navigation 3, आपके ऐप्लिकेशन के यूज़र इंटरफ़ेस (यूआई) के फ़्लो को सीन के ज़रिए मैनेज करने के लिए, एक दमदार और फ़्लेक्सिबल सिस्टम उपलब्ध कराता है. सीन की मदद से, पसंद के मुताबिक लेआउट बनाए जा सकते हैं. साथ ही, अलग-अलग स्क्रीन साइज़ के हिसाब से लेआउट को अडजस्ट किया जा सकता है. इसके अलावा, एक साथ कई पैनल दिखाने वाले जटिल अनुभवों को आसानी से मैनेज किया जा सकता है.

सीन के बारे में जानकारी

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>>: यह प्रॉपर्टी उन NavEntry को तय करती है जो मौजूदा Scene से "पीछे" जाने की कार्रवाई करने पर दिखेंगी. यह प्रॉपर्टी, पहले से अनुमान लगाकर बैक स्टेट का सही अनुमान लगाने के लिए ज़रूरी है. इससे NavDisplay को पिछली सही स्थिति का अनुमान लगाने और उसमें ट्रांज़िशन करने में मदद मिलती है. यह स्थिति, अलग क्लास, कुंजी या दोनों के साथ कोई सीन हो सकती है.
  • content: @Composable () -> Unit: यह कंपोज़ेबल फ़ंक्शन है. इसमें यह तय किया जाता है कि Scene अपने entries और उस Scene के लिए खास तौर पर बने यूज़र इंटरफ़ेस (यूआई) के आस-पास के एलिमेंट को कैसे रेंडर करेगा.
  • metadata: Map<String, Any>: यह अन्य लाइब्रेरी कॉम्पोनेंट को सीन के बारे में खास जानकारी देता है, जैसे NavDisplay. डिफ़ॉल्ट रूप से, यह entries में मौजूद आखिरी NavEntry का metadata दिखाता है.

पसंद के मुताबिक बनाए गए सीन में equals और hashCode को लागू करना

पसंद के मुताबिक बनाए गए Scene को लागू करने के लिए, equals और hashCode को सही तरीके से लागू करना ज़रूरी है, ताकि ये काम किए जा सकें:

  • सीन की स्थिति में ट्रांज़िशन: सीन की रणनीतियों की सूची से हर बार सीन का हिसाब लगाने पर, NavDisplay नए सीन और पिछले सीन की समानता की जांच करता है. अगर उसे कोई बदलाव दिखता है, तो वह सीन के बीच ट्रांज़िशन के लिए इस्तेमाल किए गए SeekableTransition को अपडेट करता है. इससे सीन की लाइफ़साइकल की स्थिति पर असर पड़ता है.
  • ओवरले मैनेजमेंट: डायलॉग जैसे OverlayScenes के लिए, NavDisplay ओवरले की लाइफ़साइकल और एक्ज़िट ऐनिमेशन को ट्रैक करने के लिए, सीन ऑब्जेक्ट को key के तौर पर इस्तेमाल करता है.

इस बात की पुष्टि करें कि सीन के कॉन्टेंट को तय करने वाली सभी प्रॉपर्टी, जैसे कि key, entries, और previousEntries, equals और hashCode को लागू करने के दौरान शामिल की गई हों. आम तौर पर, इन तरीकों को लागू करने के दौरान, कॉलबैक (जैसे कि onBack) शामिल न करें, क्योंकि इनसे लॉजिकल सीन की स्थिति में बदलाव किए बिना, इंस्टेंस में बदलाव किया जा सकता है.

सीन की रणनीतियों के बारे में जानकारी

एक SceneStrategy एक ऐसा तरीका है जिससे यह तय किया जाता है कि बैक स्टैक से NavEntry की दी गई सूची को कैसे व्यवस्थित किया जाए और उसे Scene में कैसे ट्रांज़िशन किया जाए. असल में, मौजूदा बैक स्टैक एंट्री दिखाने पर, SceneStrategy खुद से ये दो अहम सवाल पूछता है:

  1. क्या मैं इन एंट्री से कोई Scene बना सकता/सकती हूं? अगर SceneStrategy यह तय करता है कि वह दी गई NavEntry को हैंडल कर सकता है और कोई काम का Scene बना सकता है (उदाहरण के लिए, कोई डायलॉग या एक साथ कई पैनल दिखाने वाला लेआउट), तो वह आगे बढ़ता है. वरना, वह null दिखाता है. इससे अन्य रणनीतियों को Scene बनाने का मौका मिलता है.
  2. अगर हां, तो मुझे उन एंट्री को Scene? में कैसे व्यवस्थित करना चाहिए? जब कोई SceneStrategy एंट्री को हैंडल करने का फ़ैसला लेता है, तो वह बनाने और यह तय करने की ज़िम्मेदारी लेता है कि बताई गई NavEntry को उस Scene में कैसे दिखाया जाएगा.Scene

का मुख्य हिस्सा, उसका तरीका है:SceneStrategycalculateScene

@Composable
public fun calculateScene(
    entries: List<NavEntry<T>>,
    onBack: (count: Int) -> Unit,
): Scene<T>?

यह तरीका, SceneStrategyScope पर एक एक्सटेंशन फ़ंक्शन है. यह बैक स्टैक से मौजूदा List<NavEntry<T>> लेता है. अगर यह दी गई एंट्री से कोई Scene<T> बना सकता है, तो उसे लौटाना चाहिए. अगर ऐसा नहीं हो सकता, तो null लौटाना चाहिए.

SceneStrategyScope की ज़िम्मेदारी, उन सभी ज़रूरी नहीं वाले आर्ग्युमेंट को बनाए रखना है जिनकी ज़रूरत SceneStrategy को हो सकती है. जैसे, onBack कॉलबैक.

सीन और सीन की रणनीतियां एक साथ कैसे काम करती हैं

NavDisplay एक ऐसा कंपोज़ेबल है जो आपके बैक स्टैक को मॉनिटर करता है. साथ ही, सही Scene तय करने और उसे रेंडर करने के लिए, एक या उससे ज़्यादा SceneStrategy का इस्तेमाल करता है.

NavDisplay का sceneStrategies पैरामीटर, SceneStrategy इंस्टेंस की एक सूची की उम्मीद करता है. इन इंस्टेंस की ज़िम्मेदारी, दिखाने के लिए Scene का हिसाब लगाना होती है. अगर दी गई रणनीतियों से कोई Scene नहीं बनता है, तो NavDisplay डिफ़ॉल्ट रूप से SinglePaneSceneStrategy का इस्तेमाल करता है.

यहां इंटरैक्शन की जानकारी दी गई है:

  • जब अपने बैक स्टैक में कुंजियां जोड़ी या हटाई जाती हैं (उदाहरण के लिए, backStack.add() या backStack.removeLastOrNull() का इस्तेमाल करके), तो NavDisplay इन बदलावों को मॉनिटर करता है.
  • NavDisplay मौजूदा सूची को NavEntrys (बैक स्टैक की कुंजियों से ली गई) को कॉन्फ़िगर किए गए sceneStrategies को क्रम से पास करता है. साथ ही, हर सूची पर calculateScene को तब तक कॉल करता है, जब तक कोई Scene नहीं मिल जाता.
  • जब कोई 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)
        )
}

उदाहरण: सूची-जानकारी वाला बुनियादी लेआउट (पसंद के मुताबिक बनाया गया सीन और रणनीति)

इस उदाहरण में, सूची-जानकारी वाला लेआउट बनाने का तरीका बताया गया है. यह लेआउट, इन दो शर्तों के आधार पर चालू होता है:

  1. विंडो की चौड़ाई इतनी होनी चाहिए कि दो पैनल दिखाए जा सकें. यानी, कम से कम WIDTH_DP_MEDIUM_LOWER_BOUND होनी चाहिए.
  2. बैक स्टैक में ऐसी एंट्री होनी चाहिए जिन्होंने खास मेटाडेटा का इस्तेमाल करके, सूची-जानकारी वाले लेआउट में दिखाए जाने की अनुमति दी हो.

यहां दिया गया स्निपेट, ListDetailScene.kt का सोर्स कोड है. इसमें ListDetailScene और ListDetailSceneStrategy, दोनों शामिल हैं:

// --- 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)
        }
    }
}

अपने NavDisplay में इस ListDetailSceneStrategy का इस्तेमाल करने के लिए, अपने entryProvider कॉल में बदलाव करें. साथ ही, सूची लेआउट के तौर पर दिखाई जाने वाली एंट्री के लिए, ListDetailScene.listPane() मेटाडेटा और जानकारी लेआउट के तौर पर दिखाई जाने वाली एंट्री के लिए, ListDetailScene.detailPane() मेटाडेटा शामिल करें. इसके बाद, ListDetailSceneStrategy() को अपनी sceneStrategy के तौर पर दें. साथ ही, एक पैनल वाले सीन के लिए डिफ़ॉल्ट फ़ॉलबैक का इस्तेमाल करें:

// 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)
}

अगर आपको सूची-जानकारी वाला अपना सीन नहीं बनाना है, तो मटीरियल सूची-जानकारी वाला सीन इस्तेमाल किया जा सकता है. इसमें काम की जानकारी और प्लेसहोल्डर की सुविधा मिलती है. इसके बारे में अगले सेक्शन में बताया गया है.

मटीरियल अडैप्टिव सीन में सूची-जानकारी वाला कॉन्टेंट दिखाना

सूची-जानकारी वाले इस्तेमाल के उदाहरण के लिए, androidx.compose.material3.adaptive:adaptive-navigation3 आर्टफ़ैक्ट, ListDetailSceneStrategy उपलब्ध कराता है. इससे सूची-जानकारी वाला Scene बनता है. यह Scene, एक साथ कई पैनल दिखाने की जटिल व्यवस्थाओं (सूची, जानकारी, और अतिरिक्त पैनल) को अपने-आप हैंडल करता है. साथ ही, विंडो के साइज़ और डिवाइस की स्थिति के हिसाब से, उन्हें अडजस्ट करता है.

मटीरियल सूची-जानकारी वाला Scene बनाने के लिए, यह तरीका अपनाएं:

  1. डिपेंडेंसी जोड़ना: अपने प्रोजेक्ट की androidx.compose.material3.adaptive:adaptive-navigation3 फ़ाइल में, build.gradle.kts शामिल करें.
  2. ListDetailSceneStrategy मेटाडेटा के साथ अपनी एंट्री तय करना: सही पैनल में दिखाने के लिए, अपने NavEntrys को मार्क करने के लिए, listPane(), detailPane(), और extraPane() का इस्तेमाल करें. listPane() हेल्पर की मदद से, कोई आइटम न चुने जाने पर detailPlaceholder भी तय किया जा सकता है.
  3. का इस्तेमाल करना rememberListDetailSceneStrategy(): यह कंपोज़ेबल फ़ंक्शन पहले से कॉन्फ़िगर किया गया ListDetailSceneStrategy उपलब्ध कराता है. इसका इस्तेमाल NavDisplay कर सकता है.

यहां दिया गया स्निपेट, ListDetailSceneStrategy के इस्तेमाल को दिखाने वाली एक सैंपल Activity है:

@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")
                        }
                    }
                )
            }
        }
    }
}

पहली इमेज. मटीरियल सूची-जानकारी वाले सीन में चल रहे कॉन्टेंट का उदाहरण.