الرسومات البيانية المدمَجة

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

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

مثال

يجب أن يبدأ الرسم البياني للتنقّل على المستوى الأعلى في تطبيقك بالوجهة الأولية التي تظهر للمستخدم عند تشغيل التطبيق، ويجب أن يتضمّن الوجهات التي تظهر له أثناء تنقّله في تطبيقك.

الشكل 1. رسم بياني للتنقّل في المستوى الأعلى

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

وفقًا لأفضل الممارسات، اضبط شاشة المباراة لتكون وجهة البدء في الرسم البياني للتنقّل ذي المستوى الأعلى، وانقل شاشتَي العنوان والتسجيل إلى رسم بياني متداخل، كما هو موضّح في الشكل 1:

الشكل 2. يحتوي الآن الرسم البياني للتنقّل من المستوى الأعلى على رسم بياني متداخل.

عند فتح شاشة المطابقة، تحقَّق مما إذا كان هناك مستخدم مسجَّل. إذا لم يكن المستخدم مسجّلاً، انتقِل به إلى شاشة التسجيل.

لمزيد من المعلومات حول سيناريوهات التنقّل الشرطي، يُرجى الاطّلاع على التنقّل الشرطي.

إنشاء

لإنشاء رسم بياني للتنقّل متداخل باستخدام Compose، استخدِم الدالة NavGraphBuilder.navigation(). يمكنك استخدام navigation() تمامًا مثل الدالتَين NavGraphBuilder.composable() وNavGraphBuilder.dialog() عند إضافة وجهات إلى رسم بياني.

والفرق الأساسي هو أنّ navigation تنشئ رسمًا بيانيًا متداخلاً بدلاً من وجهة جديدة. بعد ذلك، يمكنك استدعاء composable() وdialog() ضمن دالة lambda الخاصة بـ navigation() لإضافة وجهات إلى الرسم البياني المتداخل.

لننظر إلى كيفية تنفيذ المقتطف التالي للرسم البياني في الشكل 2 باستخدام Compose:

// Routes
@Serializable object Title
@Serializable object Register

// Route for nested graph
@Serializable object Game

// Routes inside nested graph
@Serializable object Match
@Serializable object InGame
@Serializable object ResultsWinner
@Serializable object GameOver

NavHost(navController, startDestination = Title) {
   composable<Title> {
       TitleScreen(
           onPlayClicked = { navController.navigate(route = Register) },
           onLeaderboardsClicked = { /* Navigate to leaderboards */ }
       )
   }
   composable<Register> {
       RegisterScreen(
           onSignUpComplete = { navController.navigate(route = Game) }
       )
   }
   navigation<Game>(startDestination = Match) {
       composable<Match> {
           MatchScreen(
               onStartGame = { navController.navigate(route = InGame) }
           )
       }
       composable<InGame> {
           InGameScreen(
               onGameWin = { navController.navigate(route = ResultsWinner) },
               onGameLose = { navController.navigate(route = GameOver) }
           )
       }
       composable<ResultsWinner> {
           ResultsWinnerScreen(
               onNextMatchClicked = {
                   navController.navigate(route = Match) {
                       popUpTo(route = Match) { inclusive = true }
                   }
               },
               onLeaderboardsClicked = { /* Navigate to leaderboards */ }
           )
       }
       composable<GameOver> {
           GameOverScreen(
               onTryAgainClicked = {
                   navController.navigate(route = Match) {
                       popUpTo(route = Match) { inclusive = true }
                   }
               }
           )
       }
   }
}

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

navController.navigate(route = Match)
.

XML

عند استخدام XML، يمكنك استخدام "محرِّر التنقّل" لإنشاء الرسم البياني المتداخل. لإجراء ذلك، اتّبِع الخطوات التالية:

  1. في "محرّر التنقّل" (Navigation Editor)، اضغط مع الاستمرار على مفتاح Shift، ثم انقر على الوجهات التي تريد تضمينها في الرسم البياني المتداخل.
  2. انقر بزر الماوس الأيمن لفتح قائمة السياق، ثم اختَر نقل إلى الرسم البياني المتداخل (Move to Nested Graph) > رسم بياني جديد (New Graph). يتم تضمين الوجهات في رسم بياني متداخل. يعرض الشكل 2 رسمًا بيانيًا متداخلًا في أداة Navigation Editor:

    الشكل 2. رسم بياني متداخل في "محرّر التنقّل"
  3. انقر على الرسم البياني المتداخل. تظهر السمات التالية في لوحة السمات:

    • النوع، الذي يحتوي على "رسم بياني متداخل"
    • المعرّف: يحتوي على معرّف يحدّده النظام للرسم البياني المتداخل. يُستخدَم هذا المعرّف للإشارة إلى الرسم البياني المتداخل من الرمز البرمجي.
  4. انقر مرّتين على الرسم البياني المتداخل لعرض وجهاته.

  5. انقر على علامة التبويب نص للتبديل إلى عرض XML. تمت إضافة رسم بياني متداخل للتنقّل إلى الرسم البياني. يحتوي الرسم البياني للتنقّل هذا على navigation عناصره الخاصة بالإضافة إلى المعرّف الخاص به وسمة startDestination تشير إلى الوجهة الأولى في الرسم البياني المتداخل:

    <?xml version="1.0" encoding="utf-8"?>
    <navigation xmlns:app="http://schemas.android.com/apk/res-auto"
       xmlns:tools="http://schemas.android.com/tools"
       xmlns:android="http://schemas.android.com/apk/res/android"
       app:startDestination="@id/mainFragment">
       <fragment
           android:id="@+id/mainFragment"
           android:name="com.example.cashdog.cashdog.MainFragment"
           android:label="fragment_main"
           tools:layout="@layout/fragment_main" >
           <action
               android:id="@+id/action_mainFragment_to_sendMoneyGraph"
               app:destination="@id/sendMoneyGraph" />
           <action
               android:id="@+id/action_mainFragment_to_viewBalanceFragment"
               app:destination="@id/viewBalanceFragment" />
       </fragment>
       <fragment
           android:id="@+id/viewBalanceFragment"
           android:name="com.example.cashdog.cashdog.ViewBalanceFragment"
           android:label="fragment_view_balance"
           tools:layout="@layout/fragment_view_balance" />
       <navigation android:id="@+id/sendMoneyGraph" app:startDestination="@id/chooseRecipient">
           <fragment
               android:id="@+id/chooseRecipient"
               android:name="com.example.cashdog.cashdog.ChooseRecipient"
               android:label="fragment_choose_recipient"
               tools:layout="@layout/fragment_choose_recipient">
               <action
                   android:id="@+id/action_chooseRecipient_to_chooseAmountFragment"
                   app:destination="@id/chooseAmountFragment" />
           </fragment>
           <fragment
               android:id="@+id/chooseAmountFragment"
               android:name="com.example.cashdog.cashdog.ChooseAmountFragment"
               android:label="fragment_choose_amount"
               tools:layout="@layout/fragment_choose_amount" />
       </navigation>
    </navigation>
    
  6. في الرمز البرمجي، مرِّر رقم تعريف المورد للإجراء الذي يربط الرسم البياني الأساسي بالرسم البياني المتداخل:

KotlinJava
view.findNavController().navigate(R.id.action_mainFragment_to_sendMoneyGraph)
Navigation.findNavController(view).navigate(R.id.action_mainFragment_to_sendMoneyGraph);
  1. في علامة التبويب تصميم، ارجع إلى الرسم البياني الجذر من خلال النقر على الجذر.

الرجوع إلى رسومات بيانية أخرى للتنقّل باستخدام include

هناك طريقة أخرى لتقسيم بنية الرسم البياني إلى وحدات وهي تضمين رسم بياني داخل رسم بياني آخر باستخدام العنصر <include> في الرسم البياني الرئيسي للتنقّل. يتيح ذلك تحديد الرسم البياني المضمّن في وحدة أو مشروع منفصلين تمامًا، ما يزيد من إمكانية إعادة الاستخدام إلى أقصى حد.

يوضّح المقتطف التالي كيفية استخدام <include>:

<!-- (root) nav_graph.xml -->
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/nav_graph"
    app:startDestination="@id/fragment">

    <include app:graph="@navigation/included_graph" />

    <fragment
        android:id="@+id/fragment"
        android:name="com.example.myapplication.BlankFragment"
        android:label="Fragment in Root Graph"
        tools:layout="@layout/fragment_blank">
        <action
            android:id="@+id/action_fragment_to_second_graph"
            app:destination="@id/second_graph" />
    </fragment>

    ...
</navigation>
<!-- included_graph.xml -->
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/second_graph"
    app:startDestination="@id/includedStart">

    <fragment
        android:id="@+id/includedStart"
        android:name="com.example.myapplication.IncludedStart"
        android:label="fragment_included_start"
        tools:layout="@layout/fragment_included_start" />
</navigation>