اصول اولیه را درک و اجرا کنید

ناوبری، نحوه حرکت کاربران در برنامه شما را توصیف می‌کند. کاربران معمولاً با ضربه زدن یا کلیک کردن روی عناصر رابط کاربری، با آنها تعامل دارند و برنامه با نمایش محتوای جدید پاسخ می‌دهد. اگر کاربر بخواهد به محتوای قبلی برگردد، از ژست برگشت یا ضربه زدن روی دکمه برگشت استفاده می‌کند.

مدل‌سازی وضعیت ناوبری

یک روش مناسب برای مدل‌سازی این رفتار، استفاده از پشته‌ای از محتوا است. وقتی کاربر به سمت محتوای جدید حرکت می‌کند، آن محتوا به بالای پشته منتقل می‌شود. وقتی از آن محتوا برمی‌گردد ، از پشته خارج شده و محتوای قبلی نمایش داده می‌شود. در اصطلاحات ناوبری، این پشته معمولاً به عنوان پشته پشتی شناخته می‌شود زیرا نشان‌دهنده محتوایی است که کاربر می‌تواند به آن برگردد .

یک دکمه‌ی عملیاتی کیبورد نرم‌افزاری (یک آیکون تیک) که با دایره‌ی قرمز مشخص شده است.
شکل ۱. نموداری که نشان می‌دهد چگونه back stack با رویدادهای ناوبری کاربر تغییر می‌کند.

یک پشته ایجاد کنید

در ناوبری ۳، پشته پشتی در واقع حاوی محتوا نیست. در عوض، حاوی ارجاعاتی به محتوا است که به عنوان کلید شناخته می‌شوند. کلیدها می‌توانند از هر نوعی باشند اما معمولاً کلاس‌های داده ساده و قابل سریال‌سازی هستند. استفاده از ارجاعات به جای محتوا مزایای زیر را دارد:

  • با فشار دادن کلیدها روی پشته پشتی، پیمایش آسان است.
  • تا زمانی که کلیدها قابل سریال‌سازی باشند، می‌توان back stack را در حافظه دائمی ذخیره کرد و به آن اجازه داد تا از تغییرات پیکربندی و مرگ فرآیند جان سالم به در ببرد. این مهم است زیرا کاربران انتظار دارند برنامه شما را ترک کنند، بعداً به آن برگردند و با همان محتوایی که نمایش داده می‌شود، از جایی که متوقف شده‌اند، ادامه دهند. برای اطلاعات بیشتر به Save your back stack مراجعه کنید.

یک مفهوم کلیدی در API ناوبری ۳ این است که شما مالک پشته (back stack) هستید. این کتابخانه:

  • انتظار می‌رود که پشته پشتی شما یک List<T> با وضعیت snapshot باشد، که در آن T نوع keys پشته پشتی شما است. می‌توانید Any استفاده کنید یا کلیدهای خود را با نوع قوی‌تر ارائه دهید. وقتی اصطلاحات "push" یا "pop" را می‌بینید، پیاده‌سازی اساسی آن اضافه کردن یا حذف کردن موارد از انتهای یک لیست است.
  • پشته (stack) شما را مشاهده می‌کند و وضعیت آن را با استفاده از NavDisplay در رابط کاربری (UI) منعکس می‌کند.

مثال زیر نحوه ایجاد کلیدها و یک پشته پشتی و تغییر پشته پشتی در پاسخ به رویدادهای ناوبری کاربر را نشان می‌دهد:

// Define keys that will identify content
data object ProductList
data class ProductDetail(val id: String)

@Composable
fun MyApp() {

    // Create a back stack, specifying the key the app should start with
    val backStack = remember { mutableStateListOf<Any>(ProductList) }

    // Supply your back stack to a NavDisplay so it can reflect changes in the UI
    // ...more on this below...

    // Push a key onto the back stack (navigate forward), the navigation library will reflect the change in state
    backStack.add(ProductDetail(id = "ABC"))

    // Pop a key off the back stack (navigate back), the navigation library will reflect the change in state
    backStack.removeLastOrNull()
}

حل کلیدهای محتوا

محتوا در Navigation 3 با استفاده از NavEntry مدل‌سازی می‌شود، که کلاسی شامل یک تابع قابل ترکیب است. این کلاس یک مقصد را نشان می‌دهد - یک قطعه واحد از محتوا که کاربر می‌تواند به جلو و عقب حرکت کند.

یک NavEntry همچنین می‌تواند شامل فراداده - اطلاعاتی در مورد محتوا - باشد. این فراداده‌ها می‌توانند توسط اشیاء نگهدارنده، مانند NavDisplay ، خوانده شوند تا به آنها در تصمیم‌گیری در مورد نحوه نمایش محتوای NavEntry کمک کنند. به عنوان مثال، فراداده‌ها می‌توانند برای لغو انیمیشن‌های پیش‌فرض برای یک NavEntry خاص استفاده شوند. metadata NavEntry نقشه‌ای از کلیدهای String به مقادیر Any است که امکان ذخیره‌سازی داده‌های متنوع را فراهم می‌کند.

برای تبدیل یک key به NavEntry ، یک Entry Provider ایجاد کنید. این تابعی است که یک key می‌پذیرد و یک NavEntry برای آن key برمی‌گرداند. معمولاً هنگام ایجاد NavDisplay به عنوان یک پارامتر لامبدا تعریف می‌شود.

دو راه برای ایجاد یک Entry Provider وجود دارد، یا با ایجاد مستقیم یک تابع لامبدا، یا با استفاده از entryProvider DSL.

مستقیماً یک تابع Entry Provider ایجاد کنید

شما معمولاً با استفاده از دستور when یک تابع Entry Provider ایجاد می‌کنید که برای هر یک از کلیدهای شما یک شاخه دارد.

entryProvider = { key ->
    when (key) {
        is ProductList -> NavEntry(key) { Text("Product List") }
        is ProductDetail -> NavEntry(
            key,
            metadata = mapOf("extraDataKey" to "extraDataValue")
        ) { Text("Product ${key.id} ") }

        else -> {
            NavEntry(Unit) { Text(text = "Invalid Key: $it") }
        }
    }
}

از entryProvider DSL استفاده کنید

DSL entryProvider می‌تواند با اجتناب از نیاز به آزمایش روی هر یک از انواع کلید شما و ساخت یک NavEntry برای هر کدام، تابع lambda شما را ساده کند. برای این کار از تابع سازنده entryProvider استفاده کنید. همچنین شامل رفتار پیش‌فرض fallback (ارسال خطا) در صورت عدم یافتن کلید است.

entryProvider = entryProvider {
    entry<ProductList> { Text("Product List") }
    entry<ProductDetail>(
        metadata = mapOf("extraDataKey" to "extraDataValue")
    ) { key -> Text("Product ${key.id} ") }
}

به موارد زیر از قطعه کد توجه کنید:

  • entry برای تعریف یک NavEntry با نوع داده شده و محتوای قابل ترکیب استفاده می‌شود.
  • entry یک پارامتر metadata برای تنظیم NavEntry.metadata می‌پذیرد.

پشته را نمایش دهید

پشته پشتی نشان‌دهنده وضعیت ناوبری برنامه شماست. هر زمان که پشته پشتی تغییر کند، رابط کاربری برنامه باید وضعیت جدید پشته پشتی را منعکس کند. در ناوبری ۳، یک NavDisplay پشته پشتی شما را مشاهده می‌کند و رابط کاربری خود را بر اساس آن به‌روزرسانی می‌کند. آن را با پارامترهای زیر بسازید:

  • پشته پشتی شما - این باید از نوع SnapshotStateList<T> باشد، که در آن T نوع کلیدهای پشته پشتی شما است. این یک List قابل مشاهده است به طوری که هنگام تغییر، ترکیب NavDisplay را فعال می‌کند.
  • یک entryProvider برای تبدیل کلیدهای موجود در back stack شما به اشیاء NavEntry .
  • به صورت اختیاری، یک لامبدا به پارامتر onBack بدهید. این زمانی فراخوانی می‌شود که کاربر یک رویداد back را فعال کند.

مثال زیر نحوه ایجاد یک NavDisplay را نشان می‌دهد.

data object Home
data class Product(val id: String)

@Composable
fun NavExample() {

    val backStack = remember { mutableStateListOf<Any>(Home) }

    NavDisplay(
        backStack = backStack,
        onBack = { backStack.removeLastOrNull() },
        entryProvider = { key ->
            when (key) {
                is Home -> NavEntry(key) {
                    ContentGreen("Welcome to Nav3") {
                        Button(onClick = {
                            backStack.add(Product("123"))
                        }) {
                            Text("Click to navigate")
                        }
                    }
                }

                is Product -> NavEntry(key) {
                    ContentBlue("Product ${key.id} ")
                }

                else -> NavEntry(Unit) { Text("Unknown route") }
            }
        }
    )
}

به طور پیش‌فرض، NavDisplay بالاترین NavEntry در پشته پشتی در یک طرح تک‌صفحه‌ای نشان می‌دهد. ضبط زیر این برنامه را در حال اجرا نشان می‌دهد:

رفتار پیش‌فرض `NavDisplay` با دو مقصد.
شکل ۲. رفتار پیش‌فرض NavDisplay با دو مقصد.

چرخه حیات مقصد

NavDisplay از LifecycleOwner سفارشی برای محدود کردن وضعیت چرخه عمر یک NavEntry بر اساس محدودیت‌های سطح Scene و محدودیت‌های سطح Entry استفاده می‌کند.

برای اطلاعات بیشتر در مورد چرخه‌های حیات در Compose، به Lifecycle در Jetpack Compose مراجعه کنید.

محدودیت‌های چرخه حیات در سطح صحنه

NavDisplay چرخه حیات Scene فعال را مدیریت می‌کند. محدودیت‌های سطح صحنه به شرح زیر تعیین می‌شوند:

برای صحنه‌های بدون همپوشانی:

  • RESUMED : فقط زمانی مجاز است که انتقال صحنه به حالت عادی برگشته باشد و هیچ صحنه‌ی هم‌پوشانی فعالی روی آن نمایش داده نشود.
  • STARTED ): در حین انتقال صحنه، مانند هنگام حرکت به جلو یا عقب، یا زمانی که با یک پوشش پوشانده شده است، روی STARTED محدود می‌شود.

برای صحنه‌های روی هم افتاده ، مانند دیالوگ‌ها یا صفحات پایینی:

  • RESUMED : فقط برای بالاترین صحنه‌ی هم‌پوشانی فعال فعلی مجاز است.
  • STARTED : برای هر صحنه‌ی پوششی زیرین که توسط یک پوشش جدیدتر پوشانده شده است، با STARTED محدود می‌شود.

وضعیت چرخه حیات سطح ورودی

این کتابخانه حداکثر وضعیت چرخه حیات هر NavEntry را بر اساس حضور آن در back stack مدیریت می‌کند:

  • RESUMED : اگر ورودی در پشته فعلی موجود باشد، چرخه حیات آن مجاز است تا RESUMED (با توجه به محدودیت سطح صحنه) ادامه یابد.
  • CREATED ): اگر ورودی دیگر در پشته پشتی نباشد ، مانند زمانی که ظاهر شده اما هنوز در حین انیمیشن‌سازی روی صفحه نمایش داده می‌شود، کتابخانه چرخه حیات آن را به شدت در CREATED محدود می‌کند. این محدودیت تضمین می‌کند که ورودی‌های پس‌زمینه یا در حال خروج، اجرای کارهای فعال مانند جمع‌آوری جریان‌ها یا راه‌اندازی کوروتین‌های محدود به حالت‌های RESUMED یا STARTED را در حین اتمام انتقال‌های خروج خود متوقف می‌کنند.

چگونه ترکیب می‌شوند

برای مثال، وضعیت نهایی چرخه حیات یک NavEntry به صورت زیر حل می‌شود:

سناریو محدودیت سطح صحنه سقف سطح مبتدی درپوش مؤثر
ورود فعال، صفحه ثابت (بدون انتقال یا همپوشانی) RESUMED RESUMED RESUMED
ورود فعال، در حین انتقال (رفتن به یا از) STARTED RESUMED STARTED
ورودی فعال، پوشیده شده توسط یک پوشش (برای مثال، یک پنجره گفتگو باز است) STARTED RESUMED STARTED
ورودی ظاهر شده، متحرک سازی شده STARTED یا RESUMED CREATED CREATED

همه چیز را کنار هم قرار دادن

نمودار زیر نحوه‌ی جریان داده‌ها بین اشیاء مختلف در Navigation 3 را نشان می‌دهد:

تصویری از نحوه‌ی جریان داده‌ها بین اشیاء مختلف در ناوبری ۳.
شکل ۳. نموداری که نحوه‌ی جریان داده‌ها از طریق اشیاء مختلف در ناوبری ۳ را نشان می‌دهد.
  1. رویدادهای ناوبری تغییرات را آغاز می‌کنند . کلیدها در پاسخ به تعاملات کاربر به back stack اضافه یا حذف می‌شوند.

  2. تغییر در وضعیت پشته پشتی، بازیابی محتوا را آغاز می‌کند . NavDisplay (یک کامپوننت که یک پشته پشتی را رندر می‌کند) پشته پشتی را مشاهده می‌کند. در پیکربندی پیش‌فرض، بالاترین ورودی پشته پشتی را در یک طرح‌بندی تک‌صفحه‌ای نمایش می‌دهد. هنگامی که کلید بالایی در پشته پشتی تغییر می‌کند، NavDisplay از این کلید برای درخواست محتوای مربوطه از ارائه‌دهنده ورودی استفاده می‌کند.

  3. ارائه‌دهنده ورودی، محتوا را فراهم می‌کند . ارائه‌دهنده ورودی تابعی است که یک کلید را به NavEntry تبدیل می‌کند. پس از دریافت یک کلید از NavDisplay ، ارائه‌دهنده ورودی، NavEntry مرتبط را ارائه می‌دهد که شامل کلید و محتوا است.

  4. محتوا نمایش داده می‌شود . NavDisplay NavEntry را دریافت کرده و محتوا را نمایش می‌دهد.