فهم الأساسيات وتطبيقها

يصف التنقل الطريقة التي يتنقل بها المستخدمون في تطبيقك. يتفاعل المستخدمون مع عناصر واجهة المستخدم، عادةً عن طريق النقر عليها، ويستجيب التطبيق من خلال عرض محتوى جديد. إذا أراد المستخدم العودة إلى المحتوى السابق، فإنه يستخدم إيماءة الرجوع أو ينقر على زر الرجوع.

حالة التنقّل في النمذجة

من الطرق المناسبة لنمذجة هذا السلوك استخدام حزمة من المحتوى. وعندما ينتقل المستخدم للأمام إلى محتوى جديد، تتم إضافته إلى أعلى الحزمة. وعندما ينتقل المستخدمون للرجوع من هذا المحتوى، تتم إزالته من الحزمة ويتم عرض المحتوى السابق. في ما يتعلق بالتنقّل، يُشار إلى هذه الحزمة عادةً باسم الأنشطة السابقة لأنّها تمثّل المحتوى الذي يمكن للمستخدم الرجوع إليه.

زر إجراء في لوحة المفاتيح الافتراضية (رمز علامة صح) محاط بدائرة حمراء
الشكل 1: مخطّط بياني يوضّح كيفية تغيُّر الأنشطة السابقة مع أحداث تنقّل المستخدم

إنشاء أنشطة سابقة

في 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. ويتم تعريفها عادةً كمَعلمة lambda عند إنشاء NavDisplay.

هناك طريقتان لإنشاء Entry Provider، إما من خلال إنشاء دالة lambda مباشرةً أو باستخدام لغة entryProvider DSL.

إنشاء دالة Entry Provider مباشرةً

يمكنك عادةً إنشاء دالة Entry Provider باستخدام عبارة 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") }
        }
    }
}

استخدام لغة entryProvider الخاصة بالنطاق (DSL)

يمكن للغة entryProvider DSL تبسيط دالة lambda من خلال تجنُّب الحاجة إلى اختبار كل نوع من أنواع المفاتيح وإنشاء 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
  • يمكنك اختياريًا تقديم دالة lambda للمَعلمة 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` مع وجهتَين
الشكل 2. NavDisplay الإعداد التلقائي مع وجهتَين

مراحل نشاط الوجهة

يستخدم NavDisplay LifecycleOwner مخصّصة لتحديد حالة مراحل نشاط 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.
الشكل 3. مخطّط بياني يوضّح كيفية تدفق البيانات من خلال عناصر مختلفة في Navigation 3.
  1. أحداث التنقّل تبدأ التغييرات. تتم إضافة المفاتيح أو إزالتها من الأنشطة السابقة استجابةً لتفاعلات المستخدم.

  2. يؤدي التغيير في حالة الأنشطة السابقة إلى استرداد المحتوى. تراقب الدالة NavDisplay (دالة مركّبة تعرض الأنشطة السابقة) الأنشطة السابقة. في الإعدادات التلقائية، يعرض هذا المكوّن الإدخال الأعلى في الأنشطة السابقة في تنسيق لوحة واحدة. عندما يتغيّر المفتاح العلوي في الأنشطة السابقة، يستخدم NavDisplay هذا المفتاح لطلب المحتوى المقابل من موفّر الإدخال.

  3. يوفّر مقدّم الإدخال المحتوى. موفّر الإدخال هو دالة تحوّل مفتاحًا إلى NavEntry. عند تلقّي مفتاح من NavDisplay، يقدّم موفّر الإدخال NavEntry المرتبط الذي يحتوي على كلّ من المفتاح والمحتوى.

  4. يتم عرض المحتوى. يتلقّى NavDisplay NavEntry ويعرض المحتوى.