إنشاء تنقُّل تكيُّفي

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

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

  • شريط التنقّل إذا كان العرض أو الارتفاع مضغوطًا أو إذا كان الجهاز في وضع سطح المكتب
  • شريط التنقّل لكل ما عدا ذلك
الشكل 1. تعرض NavigationSuiteScaffold شريط تنقّل في النوافذ المصغّرة.
الشكل 2. تعرض NavigationSuiteScaffold شريط تنقّل في النوافذ الموسّعة.

إضافة عناصر تابعة

NavigationSuiteScaffold هي جزء من مكتبة حزمة التنقّل التكيّفي في Material 3. أضِف تبعية للمكتبة في ملف build.gradle الخاص بتطبيقك أو وحدتك:

Kotlin

implementation("androidx.compose.material3:material3-adaptive-navigation-suite")

Groovy

implementation 'androidx.compose.material3:material3-adaptive-navigation-suite'

إنشاء هيكل

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

enum class AppDestinations(
    @StringRes val label: Int,
    val icon: ImageVector,
    @StringRes val contentDescription: Int
) {
    HOME(R.string.home, Icons.Default.Home, R.string.home),
    FAVORITES(R.string.favorites, Icons.Default.Favorite, R.string.favorites),
    SHOPPING(R.string.shopping, Icons.Default.ShoppingCart, R.string.shopping),
    PROFILE(R.string.profile, Icons.Default.AccountBox, R.string.profile),
}

لاستخدام NavigationSuiteScaffold، عليك تتبُّع الوجهة الحالية، ويمكنك إجراء ذلك باستخدام rememberSaveable:

var currentDestination by rememberSaveable { mutableStateOf(AppDestinations.HOME) }

في المثال التالي، تستخدم المَعلمة navigationSuiteItems (type NavigationSuiteScope) الدالة item لتحديد واجهة مستخدم التنقّل لوجهة فردية. تُستخدَم واجهة المستخدم الخاصة بالوجهة في أشرطة التنقّل وأشرطة السكك الحديدية وأدراج التنقّل. لإنشاء عناصر تنقّل، عليك تكرار AppDestinations (المحدّد في المقتطف السابق):

NavigationSuiteScaffold(
    navigationSuiteItems = {
        AppDestinations.entries.forEach {
            item(
                icon = {
                    Icon(
                        it.icon,
                        contentDescription = stringResource(it.contentDescription)
                    )
                },
                label = { Text(stringResource(it.label)) },
                selected = it == currentDestination,
                onClick = { currentDestination = it }
            )
        }
    }
) {
    // TODO: Destination content.
}

ضمن دالة lambda الخاصة بمحتوى الوجهة، استخدِم القيمة currentDestination لتحديد واجهة المستخدم التي سيتم عرضها. إذا كنت تستخدم مكتبة تنقّل في تطبيقك، استخدِمها هنا لعرض الوجهة المناسبة. يمكن أن تكون عبارة when كافية:

NavigationSuiteScaffold(
    navigationSuiteItems = { /*...*/ }
) {
    // Destination content.
    when (currentDestination) {
        AppDestinations.HOME -> HomeDestination()
        AppDestinations.FAVORITES -> FavoritesDestination()
        AppDestinations.SHOPPING -> ShoppingDestination()
        AppDestinations.PROFILE -> ProfileDestination()
    }
}

تغيير الألوان

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

تحدّد المَعلمة containerColor لون السطح. القيمة التلقائية هي لون الخلفية لنظام الألوان. تحدّد المَعلمة contentColor لون المحتوى على تلك المساحة. القيمة التلقائية هي لون "التشغيل" لأي عنصر محدّد في containerColor. على سبيل المثال، إذا كان containerColor يستخدم اللون background، سيستخدم contentColor اللون onBackground. لمزيد من التفاصيل حول طريقة عمل نظام الألوان، يمكنك الاطّلاع على استخدام السمات في Material Design 3 في Compose. عند تجاهل هذه القيم، استخدِم القيم المحدّدة في المظهر لكي يتوافق تطبيقك مع وضعي العرض الداكن والفاتح:

NavigationSuiteScaffold(
    navigationSuiteItems = { /* ... */ },
    containerColor = MaterialTheme.colorScheme.primary,
    contentColor = MaterialTheme.colorScheme.onPrimary,
) {
    // Content...
}

يتم رسم واجهة مستخدم التنقّل أمام سطح NavigationSuiteScaffold. يتم توفير القيم التلقائية لألوان واجهة المستخدم من خلال NavigationSuiteDefaults.colors()، ولكن يمكنك أيضًا إلغاء هذه القيم. على سبيل المثال، إذا كنت تريد أن تكون خلفية شريط التنقّل شفافة، ولكن تريد أن تكون القيم الأخرى هي القيم التلقائية، يمكنك إلغاء navigationBarContainerColor:

NavigationSuiteScaffold(
    navigationSuiteItems = { /* ... */ },
    navigationSuiteColors = NavigationSuiteDefaults.colors(
        navigationBarContainerColor = Color.Transparent,
    )
) {
    // Content...
}

في النهاية، يمكنك تخصيص كل عنصر في واجهة مستخدم التنقّل. عند استدعاء الدالة item، يمكنك تمرير مثيل من NavigationSuiteItemColors. يحدّد الصف ألوان العناصر في شريط التنقّل وقضيب التنقّل ودرج التنقّل. وهذا يعني أنّه يمكنك استخدام ألوان متطابقة في كل نوع من أنواع واجهات المستخدم الخاصة بالتنقّل، أو يمكنك تغيير الألوان حسب احتياجاتك. حدِّد الألوان على مستوى NavigationSuiteScaffold لاستخدام نسخة الكائن نفسها لجميع العناصر واستدعِ الدالة NavigationSuiteDefaults.itemColors() لتجاوز العناصر التي تريد تغييرها فقط:

val myNavigationSuiteItemColors = NavigationSuiteDefaults.itemColors(
    navigationBarItemColors = NavigationBarItemDefaults.colors(
        indicatorColor = MaterialTheme.colorScheme.primaryContainer,
        selectedIconColor = MaterialTheme.colorScheme.onPrimaryContainer
    ),
)

NavigationSuiteScaffold(
    navigationSuiteItems = {
        AppDestinations.entries.forEach {
            item(
                icon = {
                    Icon(
                        it.icon,
                        contentDescription = stringResource(it.contentDescription)
                    )
                },
                label = { Text(stringResource(it.label)) },
                selected = it == currentDestination,
                onClick = { currentDestination = it },
                colors = myNavigationSuiteItemColors,
            )
        }
    },
) {
    // Content...
}

تخصيص أنواع التنقّل

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

val adaptiveInfo = currentWindowAdaptiveInfo()
val customNavSuiteType = with(adaptiveInfo) {
    if (windowSizeClass.isWidthAtLeastBreakpoint(WIDTH_DP_EXPANDED_LOWER_BOUND)) {
        NavigationSuiteType.NavigationDrawer
    } else {
        NavigationSuiteScaffoldDefaults.calculateFromAdaptiveInfo(adaptiveInfo)
    }
}

NavigationSuiteScaffold(
    navigationSuiteItems = { /* ... */ },
    layoutType = customNavSuiteType,
) {
    // Content...
}

مراجع إضافية