درک و پیاده‌سازی اصول اولیه

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

مدل‌سازی وضعیت پیمایش

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

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

ایجاد پشته قبلی‌ها

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

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

مفهوم کلیدی در Navigation 3 API این است که شما مالک پشته قبلی‌ها هستید. کتابخانه:

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

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

// 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 خاصی استفاده کرد. NavEntry metadata نقشه‌ای از کلیدهای String به مقادیر Any است که ذخیره‌سازی داده‌های همه‌کاره را فراهم می‌کند.

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

دو روش برای ایجاد «ارائه‌دهنده ورودی» وجود دارد: یا با ایجاد مستقیم تابع لامبدا، یا بااستفاده از DSL entryProvider.

ایجاد مستقیم تابع «ارائه‌دهنده ورودی»

معمولاً تابع «ارائه‌دهنده ورودی» را بااستفاده از عبارت when ایجاد می‌کنید، با یک شاخه برای هرکدام از کلیدهایتان.

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

استفاده از DSL entryProvider

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

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 می‌پذیرد

نمایش پشته قبلی‌ها

پشته قبلی‌ها نشان‌دهنده وضعیت پیمایش برنامه شما است. هرگاه پشته قبلی‌ها تغییر می‌کند، واسط کاربر برنامه باید وضعیت جدید پشته قبلی‌ها را منعکس کند. در Navigation 3، یک NavDisplay پشته قبلی‌ها را مشاهده می‌کند و واسط کاربر را براساس آن به‌روز می‌کند. آن را با پارامترهای زیر بسازید:

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

مثال زیر نحوه ایجاد 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 از سفارشی LifecycleOwners برای محدود کردن وضعیت چرخه حیات NavEntry براساس محدودیت‌های سطح صحنه و محدودیت‌های سطح ورودی استفاده می‌کند.

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

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

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

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

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

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

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

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

کتابخانه حداکثر وضعیت چرخه حیات هر NavEntry را براساس حضور آن در پشته قبلی‌ها مدیریت می‌کند:

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

نحوه ترکیب شدن آن‌ها

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

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

درحال سرهم کردن

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

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

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

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

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