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

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 दिखाता है.

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

एक 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> बना सकता है, तो उसे Scene<T> दिखाना चाहिए. अगर यह ऐसा नहीं कर सकता, तो null दिखाना चाहिए.

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

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

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

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

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

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

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