يوفر مكوِّن التنقل لغة خاصة بالنطاق تستند إلى Kotlin،
DSL التي تعتمد على لغة ترميز Kotlin
أدوات إنشاء آمنة من النوع الآمن.
تتيح لك واجهة برمجة التطبيقات هذه إنشاء
الرسم البياني في كود Kotlin الخاص بك،
بدلاً من الدخول إلى مورد XML. يمكن أن يكون هذا مفيدًا إذا كنت ترغب في إنشاء
التنقّل في تطبيقك بشكل ديناميكي على سبيل المثال، يمكن لتطبيقك تنزيل
تخزين مؤقت لتهيئة التنقل من خدمة ويب خارجية ثم استخدام
هذه التهيئة لإنشاء رسم بياني للتنقل بشكل ديناميكي في صفحة
onCreate()
.
التبعيات
لاستخدام Kotlin DSL، أضف التبعية التالية إلى جدول بيانات
ملف build.gradle
:
Groovy
dependencies { def nav_version = "2.7.7" api "androidx.navigation:navigation-fragment-ktx:$nav_version" }
Kotlin
dependencies { val nav_version = "2.7.7" api("androidx.navigation:navigation-fragment-ktx:$nav_version") }
إنشاء رسم بياني
لنبدأ بمثال أساسي يعتمد على
تطبيق Sunflower. لهذا الغرض
على سبيل المثال، لدينا وجهتان: home
وplant_detail
. home
وجود الوجهة عند تشغيل المستخدم للتطبيق لأول مرة. هذه الوجهة
تعرض قائمة بالنباتات من حديقة المستخدم. عندما يحدد المستخدم أحد
النباتات، ينتقل التطبيق إلى وجهة plant_detail
.
يوضح الشكل 1 هذه الوجهات مع الوسيطات المطلوبة بواسطة
وجهة واحدة (plant_detail
) وإجراء، to_plant_detail
، يستخدمه التطبيق
الانتقال من home
إلى plant_detail
.
استضافة الرسم البياني Nav من Kotlin DSL
قبل أن تتمكن من إنشاء رسم بياني للتنقل في تطبيقك، تحتاج إلى مكان لاستضافة
الرسم البياني. يستخدم هذا المثال الأجزاء، لذا فهو يستضيف الرسم البياني
NavHostFragment
داخل
FragmentContainerView
:
<!-- activity_garden.xml -->
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.fragment.app.FragmentContainerView
android:id="@+id/nav_host"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:defaultNavHost="true" />
</FrameLayout>
لاحِظ أنّه لم يتم ضبط السمة app:navGraph
في هذا المثال. الرسم البياني
لم يتم تعريفه على أنه
المورد في
res/navigation
، لذا يجب ضبطه كجزء من onCreate()
.
أثناء النشاط.
في XML، يربط الإجراء معرّف الوجهة مع وسيطة واحدة أو أكثر. ومع ذلك، عند استخدام DSL للتنقل، يمكن أن يحتوي المسار على وسيطات كجزء من من المسار. وهذا يعني أنه لا يوجد مفهوم للإجراءات عند استخدام DSL.
الخطوة التالية هي تحديد بعض الثوابت التي ستستخدمها عند تحديد الرسم البياني.
إنشاء ثوابت للرسم البياني
الرسوم البيانية للتنقّل المستندة إلى XML
تحليلها كجزء من عملية تصميم Android. يتم إنشاء ثابت رقمي
لكل سمة id
محدّدة في الرسم البياني. نتج عن هذه المدة إنشاء ثابت
لا تتوفر المعرّفات عند إنشاء رسم بياني للتنقل في وقت التشغيل، وبالتالي
تستخدم ميزة DSL للتنقل سلاسل المسارات بدلاً من أرقام التعريف. يتم تمثيل كل مسار
سلسلة فريدة ويكون من الممارسات الجيدة تعريفها كثوابت لتقليل
من مخاطر الأخطاء المطبعية.
عند التعامل مع الوسيطات، تكون هذه مضمّنة في سلسلة المسار. ويمكن أن يؤدي بناء هذا المنطق في المسار، مرة أخرى، إلى تقليل مخاطر الأخطاء المطبعية التي في متناولك.
object nav_routes {
const val home = "home"
const val plant_detail = "plant_detail"
}
object nav_arguments {
const val plant_id = "plant_id"
const val plant_name = "plant_name"
}
إنشاء رسم بياني باستخدام NavGraphBuilder DSL
بمجرد تحديد الثوابت، يمكنك إنشاء شريط التنقل الرسم البياني.
val navController = findNavController(R.id.nav_host_fragment)
navController.graph = navController.createGraph(
startDestination = nav_routes.home
) {
fragment<HomeFragment>(nav_routes.home) {
label = resources.getString(R.string.home_title)
}
fragment<PlantDetailFragment>("${nav_routes.plant_detail}/{${nav_arguments.plant_id}}") {
label = resources.getString(R.string.plant_detail_title)
argument(nav_arguments.plant_id) {
type = NavType.StringType
}
}
}
في هذا المثال، تحدد دالة lambda اللاحقة وجهتين مجزأين باستخدام
fragment()
دالة إنشاء DSL. تتطلب هذه الدالة سلسلة مسار للوجهة
الذي يتم الحصول عليه من الثوابت. تقبل الدالة أيضًا الدالة الاختيارية
lambda لمزيد من الإعدادات، مثل تصنيف الوجهة، وكذلك
دوال الإنشاء المضمنة للوسيطات والروابط لمواضع معينة.
تتضمن فئة Fragment
التي
يدير واجهة المستخدم لكل وجهة يتم تمريرها كنوع بمعلمة داخل
أقواس معقوفة (<>
). يؤدي هذا الإجراء إلى التأثير نفسه الناتج عن ضبط السمة android:name
.
على وجهات مجزأة يتم تحديدها باستخدام XML.
التنقّل باستخدام الرسم البياني لـ Kotlin DSL
أخيرًا، يمكنك الانتقال من home
إلى plant_detail
باستخدام الخيار العادي.
NavController.navigate()
المكالمات:
private fun navigateToPlant(plantId: String) {
findNavController().navigate("${nav_routes.plant_detail}/$plantId")
}
في PlantDetailFragment
، يمكنك الحصول على قيمة الوسيطة على النحو الموضّح.
في المثال التالي:
val plantId: String? = arguments?.getString(nav_arguments.plant_id)
يمكن العثور على تفاصيل حول كيفية توفير الوسيطات أثناء التنقل في توفير وسيطات الوجهة.
يصف باقي هذا الدليل عناصر الرسم البياني للتنقل الشائعة والوجهات وكيفية استخدامها عند إنشاء الرسم البياني.
الوجهات
يوفر Kotlin DSL دعمًا مدمجًا لثلاثة أنواع من الوجهات:
Fragment
وActivity
وNavGraph
، لكلّ منها وجهتها الخاصة
المتوفرة لإنشاء وتهيئة دالة الامتداد
الوجهة.
وجهات التجزئة
تشير رسالة الأشكال البيانية
fragment()
يمكن تعيين معلمة دالة DSL إلى فئة التجزئة التنفيذية
سلسلة مسار فريدة لتعيينها إلى هذه الوجهة، متبوعة بعلامة lambda حيث
يمكنك توفير إعدادات إضافية كما هو موضّح في
التنقل باستخدام الرسم البياني لـ Kotlin DSL
.
fragment<FragmentDestination>(nav_routes.route_name) {
label = getString(R.string.fragment_title)
// arguments, deepLinks
}
وجهة النشاط
activity()
تستخدم دالة DSL سلسلة مسار فريدة لتعيينها إلى هذه الوجهة، ولكن
لم يتم تحديد معلمة إلى أي فئة نشاط تنفيذ. بدلاً من ذلك، يمكنك تعيين
اختيارية activityClass
في دالة lambda لاحقة. تتيح لك هذه المرونة
تحديد وجهة نشاط لنشاط يجب إطلاقه باستخدام
نية ضمنية، حيث تكون
صف النشاط الصريح لن يكون منطقيًا. وكما هو الحال مع الوجهات المجزأة،
يمكنك أيضًا ضبط تصنيف ووسيطات وروابط لصفحات معيّنة.
activity(nav_routes.route_name) {
label = getString(R.string.activity_title)
// arguments, deepLinks...
activityClass = ActivityDestination::class
}
وجهة الرسم البياني للتنقل
navigation()
يمكن استخدام دالة DSL لإنشاء
الرسم البياني المتداخل للتنقّل.
تستخدم هذه الدالة ثلاث وسيطات: مسار إلى
وتعيينه إلى الرسم البياني، ومسار وجهة بداية الرسم البياني،
lambda لتهيئة الرسم البياني بشكلٍ أكبر. تتضمن العناصر الصالحة الوجهات الأخرى،
والجدالات والروابط لمواضع معينة
تصنيفًا وصفيًا للوجهة.
يمكن أن يكون هذا التصنيف مفيدًا لربط الرسم البياني للتنقل بواجهة المستخدم.
المكونات باستخدام
واجهة مستخدم التنقل
navigation("route_to_this_graph", nav_routes.home) {
// label, other destinations, deep links
}
إتاحة الوجهات المخصّصة
إذا كنت تستخدم
نوع وجهة جديد لا
تتوافق مباشرةً مع Kotlin DSL، يمكنك إضافة هذه الوجهات إلى لغة Kotlin
DSL تستخدم addDestination()
:
// The NavigatorProvider is retrieved from the NavController
val customDestination = navigatorProvider[CustomNavigator::class].createDestination().apply {
route = Graph.CustomDestination.route
}
addDestination(customDestination)
وكبديل، يمكنك أيضًا استخدام عامل التشغيل Unary plus لإضافة قيمة الوجهة المنشأة مباشرةً إلى الرسم البياني:
// The NavigatorProvider is retrieved from the NavController
+navigatorProvider[CustomNavigator::class].createDestination().apply {
route = Graph.CustomDestination.route
}
تقديم وسيطات الوجهة
يمكن أن تحدد أي وجهة الوسيطات الاختيارية أو المطلوبة. الإجراءات
يمكن تحديدها باستخدام
argument()
على NavDestinationBuilder
، وهي الفئة الأساسية لجميع
أداة إنشاء الوجهات. تأخذ هذه الدالة اسم الوسيطة كسلسلة
و lambda التي تُستخدم لإنشاء وتكوين
NavArgument
داخل lambda، يمكنك تحديد نوع بيانات الوسيطة، وهي قيمة تلقائية إذا السارية، وما إذا كان قابلاً للإلغاء أم لا.
fragment<PlantDetailFragment>("${nav_routes.plant_detail}/{${nav_arguments.plant_id}}") {
label = getString(R.string.plant_details_title)
argument(nav_arguments.plant_id) {
type = NavType.StringType
defaultValue = getString(R.string.default_plant_id)
nullable = true // default false
}
}
وإذا تم توفير defaultValue
، يمكن استنتاج النوع. إذا كانت قيمة defaultValue
ويتم توفير type
، يجب أن تتطابق الأنواع. يمكنك الاطّلاع على
وثائق NavType المرجعية لـ
قائمة كاملة بأنواع الوسيطة المتوفرة.
توفير أنواع مخصّصة
تتضمن أنواعًا معينة، مثل
ParcelableType
أو
SerializableType
,
لا تتيح تحليل قيم القيم من السلاسل التي تستخدمها المسارات أو الروابط المؤدية إلى صفحات في التطبيق.
وذلك لأنها لا تعتمد على الانعكاس في وقت التشغيل. من خلال توفير واجهة
NavType
، يمكنك التحكّم بالضبط في كيفية تحليل نوعك من مسار أو
رابط لموضع معيّن. يتيح لك هذا استخدام
تسلسل Kotlin أو نوع آخر
المكتبات لتوفير تشفير غير انعكاسي وفك ترميز النوع المخصص الذي تختاره.
على سبيل المثال، فئة بيانات تمثل معلَمات البحث التي يتم تمريرها إلى
شاشة البحث إلى تنفيذ كل من Serializable
(لتوفير
دعم الترميز/فك التشفير) وParcelize
(لدعم الحفظ في واستعادة البيانات
من Bundle
):
@Serializable
@Parcelize
data class SearchParameters(
val searchQuery: String,
val filters: List<String>
)
يمكن كتابة NavType
مخصّص على النحو التالي:
val SearchParametersType = object : NavType<SearchParameters>(
isNullableAllowed = false
) {
override fun put(bundle: Bundle, key: String, value: SearchParameters) {
bundle.putParcelable(key, value)
}
override fun get(bundle: Bundle, key: String): SearchParameters {
return bundle.getParcelable(key) as SearchParameters
}
override fun serializeAsValue(value: SearchParameters): String {
// Serialized values must always be Uri encoded
return Uri.encode(Json.encodeToString(value))
}
override fun parseValue(value: String): SearchParameters {
// Navigation takes care of decoding the string
// before passing it to parseValue()
return Json.decodeFromString<SearchParameters>(value)
}
}
يمكن بعد ذلك استخدام هذا في Kotlin DSL مثل أي نوع آخر:
fragment<SearchFragment>(nav_routes.plant_search) {
label = getString(R.string.plant_search_title)
argument(nav_arguments.search_parameters) {
type = SearchParametersType
defaultValue = SearchParameters("cactus", emptyList())
}
}
يغلف NavType
كل من كتابة وقراءة كل حقل،
يعني هذا أنّه يجب استخدام NavType
أيضًا عند الانتقال إلى
الوجهة لضمان تطابق التنسيقات:
val params = SearchParameters("rose", listOf("available"))
val searchArgument = SearchParametersType.serializeAsValue(params)
navController.navigate("${nav_routes.plant_search}/$searchArgument")
يمكن الحصول على المَعلمة من الوسيطات في الوجهة:
val params: SearchParameters? = arguments?.getParcelable(nav_arguments.search_parameters)
روابط لصفحات معيّنة
يمكن إضافة روابط لمواضع معينة إلى أي وجهة، تمامًا كما هو الحال مع الروابط التي تستند إلى XML الرسم البياني للتنقل. جميع الإجراءات نفسها المحددة في إنشاء رابط لصفحة في التطبيق لوجهة معيّنة تطبيقها على عملية إنشاء الرابط الفاضح لصفحة في التطبيق باستخدام Kotlin DSL.
عند إنشاء رابط ضمني لصفحة في التطبيق
ومع ذلك، ليس لديك مورد تنقُّل بتنسيق XML يمكن تحليله من أجل
<deepLink>
. لذلك، لا يمكنك الاعتماد على وضع <nav-graph>
عنصر واحد في ملف AndroidManifest.xml
ويجب إضافة
فلاتر حسب النية بالشراء لنشاطك يدويًا
ويجب أن يتطابق فلتر الأهداف الذي تقدِّمه مع نمط عنوان URL الأساسي والإجراء
mimetype لروابط صفحات تطبيقك.
يمكنك توفير deeplink
أكثر تحديدًا لكل رابط فردي لصفحة في التطبيق.
باستخدام
deepLink()
DSL. تقبل هذه الدالة العنصر NavDeepLink
الذي يحتوي على السمة String
يمثل نمط معرف الموارد المنتظم (URI)، وString
الذي يمثل إجراءات intent،
String
الذي يمثل mimeType .
مثلاً:
deepLink {
uriPattern = "http://www.example.com/plants/"
action = "android.intent.action.MY_ACTION"
mimeType = "image/*"
}
ما مِن حدّ أقصى لعدد الروابط لصفحات معيّنة التي يمكنك إضافتها. في كل مرة تتصل فيها
deepLink()
يتم إلحاق رابط جديد لصفحة في التطبيق بالقائمة التي يتم الاحتفاظ بها لهذه الوجهة.
يشير ذلك المصطلح إلى سيناريو أكثر تعقيدًا للرابط الضمني لصفحة معيّنة في التطبيق والذي يحدّد أيضًا المسار. في ما يلي المعلمات المستندة إلى طلبات البحث:
val baseUri = "http://www.example.com/plants"
fragment<PlantDetailFragment>(nav_routes.plant_detail) {
label = getString(R.string.plant_details_title)
deepLink(navDeepLink {
uriPattern = "${baseUri}/{id}"
})
deepLink(navDeepLink {
uriPattern = "${baseUri}/{id}?name={plant_name}"
})
}
يمكنك استخدام استكمال السلسلة لتبسيط التعريف.
القيود
المكوّن الإضافي الوسيطات الآمنة هو
غير متوافق مع Kotlin DSL، حيث يبحث المكون الإضافي عن ملفات موارد XML
إنشاء صفَّين Directions
وArguments
مزيد من المعلومات
التحقّق من أمان نوع التنقّل لمعرفة كيفية توفير أمان الكتابة في Kotlin DSL و رمز إنشاء التنقّل.