দৃশ্য ব্যবহার করে কাস্টম লেআউট তৈরি করুন

নেভিগেশন ৩ আপনার অ্যাপের UI প্রবাহকে Scenes এর মাধ্যমে পরিচালনা করার জন্য একটি শক্তিশালী এবং নমনীয় সিস্টেম প্রবর্তন করে। Scenes আপনাকে অত্যন্ত কাস্টমাইজড লেআউট তৈরি করতে, বিভিন্ন স্ক্রিন আকারের সাথে খাপ খাইয়ে নিতে এবং জটিল মাল্টি-পেন অভিজ্ঞতা নির্বিঘ্নে পরিচালনা করতে দেয়।

দৃশ্যগুলো বুঝুন

নেভিগেশন ৩-এ, একটি Scene হল মৌলিক একক যা এক বা একাধিক NavEntry উদাহরণ রেন্ডার করে। একটি Scene আপনার UI-এর একটি স্বতন্ত্র ভিজ্যুয়াল অবস্থা বা অংশ হিসেবে ভাবুন যা আপনার ব্যাক স্ট্যাক থেকে কন্টেন্ট প্রদর্শন ধারণ এবং পরিচালনা করতে পারে।

প্রতিটি Scene উদাহরণ তার key এবং Scene শ্রেণী দ্বারা স্বতন্ত্রভাবে চিহ্নিত করা হয়। এই অনন্য সনাক্তকারী অত্যন্ত গুরুত্বপূর্ণ কারণ এটি Scene পরিবর্তনের সময় শীর্ষ-স্তরের অ্যানিমেশনকে চালিত করে।

Scene ইন্টারফেসের নিম্নলিখিত বৈশিষ্ট্য রয়েছে:

  • key: Any : এই নির্দিষ্ট Scene উদাহরণের জন্য একটি অনন্য শনাক্তকারী। এই কী, Scene ক্লাসের সাথে মিলিত হয়ে, প্রাথমিকভাবে অ্যানিমেশনের উদ্দেশ্যে স্বতন্ত্রতা নিশ্চিত করে।
  • entries: List<NavEntry<T>> : এটি NavEntry অবজেক্টের একটি তালিকা যা Scene প্রদর্শনের জন্য দায়ী। গুরুত্বপূর্ণভাবে, যদি একই NavEntry একটি ট্রানজিশনের সময় একাধিক Scenes প্রদর্শিত হয় (যেমন, একটি শেয়ার্ড এলিমেন্ট ট্রানজিশনে), তবে এর কন্টেন্ট শুধুমাত্র সাম্প্রতিকতম টার্গেট Scene দ্বারা রেন্ডার করা হবে যা এটি প্রদর্শন করছে।
  • previousEntries: List<NavEntry<T>> : এই বৈশিষ্ট্যটি NavEntry গুলি সংজ্ঞায়িত করে যা বর্তমান Scene থেকে "back" ক্রিয়া ঘটলে হবে। সঠিক ভবিষ্যদ্বাণীমূলক ব্যাক স্টেট গণনা করার জন্য এটি অপরিহার্য, যা NavDisplay পূর্ববর্তী অবস্থায় সঠিক পূর্ববর্তী অবস্থায় অনুমান করতে এবং স্থানান্তর করতে দেয়, যা একটি ভিন্ন ক্লাস এবং/অথবা কী সহ একটি দৃশ্য হতে পারে।
  • content: @Composable () -> Unit : এটি হল composable ফাংশন যেখানে আপনি সংজ্ঞায়িত করতে পারেন যে Scene কীভাবে তার entries এবং সেই Scene এর সাথে সম্পর্কিত যেকোনো UI উপাদান রেন্ডার করে।

দৃশ্য কৌশলগুলি বুঝুন

SceneStrategy হল এমন একটি প্রক্রিয়া যা নির্ধারণ করে যে ব্যাক স্ট্যাক থেকে NavEntry এর একটি প্রদত্ত তালিকা কীভাবে সাজানো হবে এবং Scene তে রূপান্তরিত করা হবে। মূলত, বর্তমান ব্যাক স্ট্যাক এন্ট্রিগুলি উপস্থাপন করার সময়, একটি SceneStrategy নিজেকে দুটি মূল প্রশ্ন জিজ্ঞাসা করে:

  1. আমি কি এই এন্ট্রিগুলি থেকে একটি Scene তৈরি করতে পারি? যদি SceneStrategy নির্ধারণ করে যে এটি প্রদত্ত NavEntry গুলি পরিচালনা করতে পারে এবং একটি অর্থপূর্ণ Scene তৈরি করতে পারে (যেমন, একটি ডায়ালগ বা একটি মাল্টি-পেন লেআউট), তাহলে এটি এগিয়ে যায়। অন্যথায়, এটি null প্রদান করে, যা অন্যান্য কৌশলগুলিকে একটি Scene তৈরি করার সুযোগ দেয়।
  2. যদি তাই হয়, তাহলে আমি কীভাবে Scene? একবার SceneStrategy এন্ট্রিগুলি পরিচালনা করার জন্য প্রতিশ্রুতিবদ্ধ হয়ে গেলে, এটি একটি Scene তৈরি করার এবং সেই Scene মধ্যে নির্দিষ্ট NavEntry কীভাবে প্রদর্শিত হবে তা নির্ধারণ করার দায়িত্ব নেয়।

একটি 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 infix ফাংশনও প্রদান করে, যা আপনাকে একাধিক কৌশল একসাথে শৃঙ্খলিত করতে দেয়। এটি একটি নমনীয় সিদ্ধান্ত গ্রহণের পাইপলাইন তৈরি করে যেখানে প্রতিটি কৌশল একটি 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: 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.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)
    }
}

আপনার NavDisplay এ এই ListDetailSceneStrategy ব্যবহার করতে, আপনার 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)
}

যদি আপনি নিজের তালিকা-বিস্তারিত দৃশ্য তৈরি করতে না চান, তাহলে আপনি ম্যাটেরিয়াল তালিকা-বিস্তারিত দৃশ্য ব্যবহার করতে পারেন, যা পরবর্তী বিভাগে প্রদর্শিত হিসাবে যুক্তিসঙ্গত বিবরণ এবং স্থানধারকগুলির জন্য সমর্থন সহ আসে।

একটি ম্যাটেরিয়াল অ্যাডাপটিভ দৃশ্যে তালিকা-বিস্তারিত বিষয়বস্তু প্রদর্শন করুন

list-detail ব্যবহারের ক্ষেত্রে , androidx.compose.material3.adaptive:adaptive-navigation3 আর্টিফ্যাক্টটি একটি ListDetailSceneStrategy প্রদান করে যা একটি list-detail Scene তৈরি করে। এই Scene স্বয়ংক্রিয়ভাবে জটিল মাল্টি-পেন বিন্যাস (তালিকা, বিশদ এবং অতিরিক্ত প্যান) পরিচালনা করে এবং উইন্ডোর আকার এবং ডিভাইসের অবস্থার উপর ভিত্তি করে সেগুলিকে অভিযোজিত করে।

একটি Material list-detail Scene তৈরি করতে, এই পদক্ষেপগুলি অনুসরণ করুন:

  1. নির্ভরতা যোগ করুন : আপনার প্রকল্পের build.gradle.kts ফাইলে androidx.compose.material3.adaptive:adaptive-navigation3 অন্তর্ভুক্ত করুন।
  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() },
                    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")
                        }
                    }
                )
            }
        }
    }
}

চিত্র ১। উপাদান তালিকা-বিস্তারিত দৃশ্যে চলমান সামগ্রীর উদাহরণ।