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

التنقّل هو عملية التفاعل مع واجهة مستخدم التطبيق للوصول إلى وجهات محتوى التطبيق. توفِّر مبادئ التنقّل في 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
        }
    )
}

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

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

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

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

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

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

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

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

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

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

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

عدد المشاهدات

تتيح لك أداة 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.

إنشاء

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

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

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

/* 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، على الموقع الإلكتروني المفوَّض الذي يتم استخدامه للحصول على 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() جميع الأجزاء في الرسم البياني نفسه للتنقّل

تجدر الإشارة إلى أنّك في حال استخدام مضيف تنقّل مدمج (انظر أعلاه)، لا يمكن للوجهات في هذا المضيف مشاركة نقاط ViewModel مع الوجهات خارج المضيف عند استخدام 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?,
) { /*...*/ }

مصادر إضافية