التنقّل في واجهات المستخدم المتجاوبة

التنقّل هو عملية التفاعل مع واجهة مستخدم التطبيق للوصول إلى وجهات محتوى التطبيق. توفر مبادئ التنقل في Android إرشادات تساعدك في إنشاء تنقل متسق وسهل الاستخدام في التطبيقات.

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

ينفِّذ مكوِّن التنقّل في Jetpack مبادئ التنقّل ويمكن استخدامه لتسهيل تطوير التطبيقات باستخدام واجهات مستخدم سريعة الاستجابة.

الشكل 1. شاشات موسّعة ومتوسطة وصغيرة مع درج تنقّل وسكة حديدية وشريط سفلي

التنقل سريع الاستجابة في واجهة المستخدم

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

فئة حجم النافذة عناصر قليلة العديد من العناصر
عرض مضغوط شريط التنقل السفلي لائحة التنقّل (الحافة البادئة أو الأسفل)
متوسط العرض شريط التنقّل لائحة التنقل (الحافة الرئيسية)
عرض موسّع شريط التنقّل لائحة تنقّل ثابتة (الحافة الرئيسية)

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

المشاهدات

<!-- res/layout/main_activity.xml -->

<androidx.constraintlayout.widget.ConstraintLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <com.google.android.material.bottomnavigation.BottomNavigationView
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        ... />

    <!-- Content view(s) -->
</androidx.constraintlayout.widget.ConstraintLayout>


<!-- res/layout-w600dp/main_activity.xml -->

<androidx.constraintlayout.widget.ConstraintLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <com.google.android.material.navigationrail.NavigationRailView
        android:layout_width="wrap_content"
        android:layout_height="0dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        ... />

    <!-- Content view(s) -->
</androidx.constraintlayout.widget.ConstraintLayout>


<!-- res/layout-w1240dp/main_activity.xml -->

<androidx.constraintlayout.widget.ConstraintLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <com.google.android.material.navigation.NavigationView
        android:layout_width="wrap_content"
        android:layout_height="0dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        ... />

    <!-- Content view(s) -->
</androidx.constraintlayout.widget.ConstraintLayout>

إنشاء

// This method should be run inside a Composable function.
val widthSizeClass = calculateWindowSizeClass(this).widthSizeClass
// You can get the height of the current window by invoking heightSizeClass instead.

@Composable
fun MyApp(widthSizeClass: WindowWidthSizeClass) {
    // Select a navigation element based on window size.
    when (widthSizeClass) {
        WindowWidthSizeClass.Compact -> { CompactScreen() }
        WindowWidthSizeClass.Medium -> { MediumScreen() }
        WindowWidthSizeClass.Expanded -> { ExpandedScreen() }
    }
}

@Composable
fun CompactScreen() {
    Scaffold(bottomBar = {
                NavigationBar {
                    icons.forEach { item ->
                        NavigationBarItem(
                            selected = isSelected,
                            onClick = { ... },
                            icon = { ... })
                    }
                }
            }
        ) {
        // Other content
    }
}

@Composable
fun MediumScreen() {
    Row(modifier = Modifier.fillMaxSize()) {
        NavigationRail {
            icons.forEach { item ->
                NavigationRailItem(
                    selected = isSelected,
                    onClick = { ... },
                    icon = { ... })
            }
        }
        // Other content
    }
}

@Composable
fun ExpandedScreen() {
    PermanentNavigationDrawer(
        drawerContent = {
            icons.forEach { item ->
                NavigationDrawerItem(
                    icon = { ... },
                    label = { ... },
                    selected = isSelected,
                    onClick = { ... }
                )
            }
        },
        content = {
            // Other content
        }
    )
}

وجهات المحتوى المتجاوب

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

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

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

يواجه التنقل كأثر جانبي لتغييرات حجم النافذة المشكلات التالية:

  • قد تظهر الوجهة القديمة (للمساحة السابقة للنافذة) قبل الانتقال إلى الوجهة الجديدة.
  • للحفاظ على إمكانية الرجوع إلى الخلف (على سبيل المثال، عند طي الجهاز وفتحه)، يجب التنقّل بين كل حجم نافذة.
  • قد يكون من الصعب الحفاظ على حالة التطبيق بين الوجهات، لأنّ التنقّل يمكن أن يؤدي إلى تدمير الحالة عند عرض حزمة Backstack.

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

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

وجهات المحتوى ذات التنسيقات البديلة

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

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

المشاهدات

تتيح لك أداة SlidingPaneLayout إنشاء وجهة تنقّل واحدة تعرض جزءَي محتوى جنبًا إلى جنب على الشاشات الكبيرة، ولكن تعرض لوحة واحدة فقط في كل مرة على الأجهزة ذات الشاشات الصغيرة، مثل الهواتف.

<!-- Single destination for list and detail. -->

<navigation ...>

    <!-- Fragment that implements SlidingPaneLayout. -->
    <fragment
        android:id="@+id/article_two_pane"
        android:name="com.example.app.ListDetailTwoPaneFragment" />

    <!-- Other destinations... -->
</navigation>

راجع إنشاء تخطيط من لوحتين للحصول على تفاصيل حول تنفيذ تخطيط تفاصيل قائمة باستخدام SlidingPaneLayout.

إنشاء

في "إنشاء"، يمكن تنفيذ عرض تفاصيل القائمة من خلال الجمع بين مركبات مركّبة بديلة في مسار واحد يستخدم فئات حجم النافذة لإنتاج التركيبة المناسبة لكل فئة حجم.

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

يتكون عرض تفاصيل القائمة من ثلاثة عناصر، على سبيل المثال:

/* Displays a list of items. */
@Composable
fun ListOfItems(
    onItemSelected: (String) -> Unit,
) { /*...*/ }

/* Displays the detail for an item. */
@Composable
fun ItemDetail(
    selectedItemId: String? = null,
) { /*...*/ }

/* Displays a list and the detail for an item side by side. */
@Composable
fun ListAndDetail(
    selectedItemId: String? = null,
    onItemSelected: (String) -> Unit,
) {
  Row {
    ListOfItems(onItemSelected = onItemSelected)
    ItemDetail(selectedItemId = selectedItemId)
  }
}

يوفر مسار التنقل الواحد إمكانية الوصول إلى عرض القائمة-التفصيلية:

@Composable
fun ListDetailRoute(
    // Indicates that the display size is represented by the expanded window size class.
    isExpandedWindowSize: Boolean = false,
    // Identifies the item selected from the list. If null, a item has not been selected.
    selectedItemId: String?,
) {
  if (isExpandedWindowSize) {
    ListAndDetail(
      selectedItemId = selectedItemId,
      /*...*/
    )
  } else {
    // If the display size cannot accommodate both the list and the item detail,
    // show one of them based on the user's focus.
    if (selectedItemId != null) {
      ItemDetail(
        selectedItemId = selectedItemId,
        /*...*/
      )
    } else {
      ListOfItems(/*...*/)
    }
  }
}

تحدِّد ListDetailRoute (وجهة التنقّل) أي العناصر الثلاثة المطلوب إرسالها: ListAndDetail لحجم النافذة الموسّعة، وListOfItems أو ItemDetail للمكوّنات المدمجة، بناءً على ما إذا تم اختيار عنصر قائمة أم لا.

المسار مضمَّن في NavHost، على سبيل المثال:

NavHost(navController = navController, startDestination = "listDetailRoute") {
  composable("listDetailRoute") {
    ListDetailRoute(isExpandedWindowSize = isExpandedWindowSize,
                    selectedItemId = selectedItemId)
  }
  /*...*/
}

يمكنك تقديم الوسيطة isExpandedWindowSize من خلال مراجعة WindowMetrics في تطبيقك.

يمكن توفير الوسيطة selectedItemId من خلال ViewModel تحافظ على حالتها في جميع أحجام النوافذ. عندما يختار المستخدِم عنصرًا من القائمة، يتم تعديل متغيّر الحالة selectedItemId:

class ListDetailViewModel : ViewModel() {

  data class ListDetailUiState(
      val selectedItemId: String? = null,
  )

  private val viewModelState = MutableStateFlow(ListDetailUiState())

  fun onItemSelected(itemId: String) {
    viewModelState.update {
      it.copy(selectedItemId = itemId)
    }
  }
}

val listDetailViewModel = ListDetailViewModel()

@Composable
fun ListDetailRoute(
    isExpandedWindowSize: Boolean = false,
    selectedItemId: String?,
    onItemSelected: (String) -> Unit = { listDetailViewModel.onItemSelected(it) },
) {
  if (isExpandedWindowSize) {
    ListAndDetail(
      selectedItemId = selectedItemId,
      onItemSelected = onItemSelected,
      /*...*/
    )
  } else {
    if (selectedItemId != null) {
      ItemDetail(
        selectedItemId = selectedItemId,
        /*...*/
      )
    } else {
      ListOfItems(
        onItemSelected = onItemSelected,
        /*...*/
      )
    }
  }
}

يتضمّن المسار أيضًا BackHandler مخصّصة عندما تشغل تفاصيل العنصر نافذة التطبيق بالكامل:

class ListDetailViewModel : ViewModel() {

  data class ListDetailUiState(
      val selectedItemId: String? = null,
  )

  private val viewModelState = MutableStateFlow(ListDetailUiState())

  fun onItemSelected(itemId: String) {
    viewModelState.update {
      it.copy(selectedItemId = itemId)
    }
  }

  fun onItemBackPress() {
    viewModelState.update {
      it.copy(selectedItemId = null)
    }
  }
}

val listDetailViewModel = ListDetailViewModel()

@Composable
fun ListDetailRoute(
    isExpandedWindowSize: Boolean = false,
    selectedItemId: String?,
    onItemSelected: (String) -> Unit = { listDetailViewModel.onItemSelected(it) },
    onItemBackPress: () -> Unit = { listDetailViewModel.onItemBackPress() },
) {
  if (isExpandedWindowSize) {
    ListAndDetail(
      selectedItemId = selectedItemId,
      onItemSelected = onItemSelected,
      /*...*/
    )
  } else {
    if (selectedItemId != null) {
      ItemDetail(
        selectedItemId = selectedItemId,
        /*...*/
      )
      BackHandler {
        onItemBackPress()
      }
    } else {
      ListOfItems(
        onItemSelected = onItemSelected,
        /*...*/
      )
    }
  }
}

إنّ الجمع بين حالة التطبيق من ViewModel ومعلومات فئة حجم النافذة يجعل اختيار المحتوى القابل للتركيب مسألة منطقية بسيطة. من خلال الحفاظ على تدفق بيانات أحادي الاتجاه، يمكن لتطبيقك استخدام مساحة العرض المتوفّرة بالكامل مع الحفاظ على حالة التطبيق.

لتنفيذ عرض القائمة التفصيلية في Compose، يُرجى الاطّلاع على نموذج JetNews على GitHub.

رسم بياني واحد للتنقّل

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

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

مضيف التنقّل المضمّن

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

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

المشاهدات

<!-- layout/two_pane_fragment.xml -->

<androidx.slidingpanelayout.widget.SlidingPaneLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/sliding_pane_layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/list_pane"
        android:layout_width="280dp"
        android:layout_height="match_parent"
        android:layout_gravity="start"/>

    <!-- Detail pane is a nested navigation host. Its graph is not connected
         to the main graph that contains the two_pane_fragment destination. -->
    <androidx.fragment.app.FragmentContainerView
        android:id="@+id/detail_pane"
        android:layout_width="300dp"
        android:layout_weight="1"
        android:layout_height="match_parent"
        android:name="androidx.navigation.fragment.NavHostFragment"
        app:navGraph="@navigation/detail_pane_nav_graph" />
</androidx.slidingpanelayout.widget.SlidingPaneLayout>

إنشاء

@Composable
fun ItemDetail(selectedItemId: String? = null) {
    val navController = rememberNavController()
    NavHost(navController, "itemSubdetail1") {
        composable("itemSubdetail1") { ItemSubdetail1(...) }
        composable("itemSubdetail2") { ItemSubdetail2(...) }
        composable("itemSubdetail3") { ItemSubdetail3(...) }
    }
}

ويختلف هذا الرسم البياني عن الرسم البياني المدمج للتنقّل، لأنّ الرسم البياني للتنقّل في NavHost المُدمَج غير مرتبط بالرسم البياني الرئيسي للتنقّل، أي أنّه لا يمكنك الانتقال مباشرةً من الوجهات في أحد الرسمَين البيانيين إلى الوجهات في رسم بياني آخر.

لمزيد من المعلومات، يُرجى الاطّلاع على الرسوم البيانية للتنقّل المُدمَجة والانتقال باستخدام ميزة "إنشاء".

حالة الحفظ

لتوفير وجهات المحتوى المتجاوب، يجب أن يحافظ تطبيقك على حالته عندما يتم تدوير الجهاز أو طيه أو عند تغيير حجم نافذة التطبيق. بشكل تلقائي، تؤدي تغييرات الإعدادات، مثل هذه التغييرات إلى إعادة إنشاء أنشطة التطبيق والأجزاء وطريقة العرض الهرمية والعناصر التي تم إنشاؤها. والطريقة المقترَحة لحفظ حالة واجهة المستخدم هي استخدام ViewModel أو rememberSaveable، واللذين يتغيران بعد تغييرات الإعدادات. (يمكنك الاطّلاع على حفظ حالات واجهة المستخدم وState وJetpack Compose.)

يجب أن تكون تغييرات الحجم قابلة للتراجع عنها، على سبيل المثال، عندما يتولّى المستخدم تدوير الجهاز ثم تدويره مرة أخرى.

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

نطاقات عرض النماذج

يقترح دليل المطوِّر نقل البيانات إلى مكوِّن التنقّل بنية أحادي النشاط يتم فيها تنفيذ الوجهات كأجزاء وتنفيذ نماذج البيانات الخاصة بها باستخدام ViewModel.

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

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

لمشاركة حالة واجهة المستخدم بين الأجزاء، يجب ضبط نطاق ViewModel على النشاط من خلال استدعاء activityViewModels() في الأجزاء (ما يعادل النشاط هو viewModels() فقط). يسمح ذلك للنشاط وأي أجزاء ترتبط به بمشاركة المثيل ViewModel. مع ذلك، في بنية النشاط الفردي، يستمر نطاق ViewModel هذا طوال مدة التطبيق، وتظل ViewModel في الذاكرة حتى إذا لم يتم استخدام أي أجزاء.

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

بدلاً من ذلك، يمكنك تحويل نطاق ViewModel إلى رسم بياني للتنقّل في NavController الحالية. أنشئ رسمًا بيانيًا مدمجًا للتنقّل لتغليف الوجهات التي تشكّل جزءًا من مسار الدفع. وبعد ذلك، في كل وجهة من وجهات التقسيم هذه، استخدِم تفويض السمة navGraphViewModels()، واضبط رقم تعريف الرسم البياني للتنقل للحصول على ViewModel المشتركة. ويضمن ذلك أنّه عندما يخرج المستخدم من مسار الدفع وكان الرسم البياني المضمَّن للتنقّل خارج النطاق، يتم تجاهل النسخة المطابقة من ViewModel ولن يتم استخدامه في عملية الدفع التالية.

المجال مندوب الموقع يمكن مشاركة جهاز "ViewModel" مع
جزء Fragment.viewModels() الجزء الحالي فقط
النشاط Activity.viewModels()

Fragment.activityViewModels()

النشاط وجميع الأجزاء المرتبطة به
الرسم البياني للتنقُّل Fragment.navGraphViewModels() جميع الأجزاء في رسم بياني للتنقّل نفسه

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

حالة الرفع

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

في القسم إنشاء ضمن وجهات المحتوى ذات التنسيقات البديلة أعلاه، تم رفع حالة عرض تفاصيل القائمة المكونة إلى ListDetailRoute بحيث يتم الحفاظ على هذه الحالة بغض النظر عن العنصر الذي يتم عرضه:

@Composable
fun ListDetailRoute(
    // Indicates that the display size is represented by the expanded window size class.
    isExpandedWindowSize: Boolean = false,
    // Identifies the item selected from the list. If null, a item has not been selected.
    selectedItemId: String?,
) { /*...*/ }

مراجع إضافية