مكوِّن التنقل هو مكتبة يمكنها إدارة التنقل المُعقَّد، والصور المتحركة للانتقال، والربط بصفحة معيّنة، والوسيطة التي تم فحصها في وقت التجميع والتي يتم تمريرها بين الشاشات في تطبيقك.
يعمل هذا المستند كدليل للأغراض العامة لنقل تطبيق موجود لاستخدام مكون التنقل.
بصورة عامة، تتضمّن عملية نقل البيانات الخطوات التالية:
نقل منطق واجهة المستخدم الخاصة بالشاشة إلى خارج الأنشطة - يمكنك إيقاف الأنشطة في واجهة المستخدم الخاصة بالتطبيق، مع التأكّد من أنّ كل نشاط لا يملك إلا منطق مكونات واجهة المستخدم العامة للتنقل، مثل
Toolbar
، مع تفويض تنفيذ كل شاشة إلى جزء أو وجهة مخصصة.دمج مكوِّن التنقل - لكل نشاط، أنشئ رسمًا بيانيًا للتنقل يحتوي على جزء واحد أو أكثر تتم إدارته من خلال هذا النشاط. استبدِل المعاملات المجزأة بعمليات مكوّن التنقل.
إضافة وجهات النشاط - استبدِل استدعاءات
startActivity()
بإجراءات تستخدم وجهات الأنشطة.دمج الأنشطة - لدمج الرسوم البيانية للتنقل في الحالات التي تشترك فيها أنشطة متعددة في تخطيط مشترك.
المتطلّبات الأساسية
يفترض هذا الدليل أنّك قد نقلت تطبيقك من قبل لاستخدام مكتبات AndroidX. في حال عدم إجراء ذلك، يمكنك نقل مشروعك لاستخدام AndroidX قبل المتابعة.
نقل منطق واجهة المستخدم الخاصة بالشاشة خارج الأنشطة
الأنشطة عبارة عن مكونات على مستوى النظام تسهِّل التفاعل الرسومي بين تطبيقك وAndroid. يتم تسجيل الأنشطة في ملف البيان للتطبيق لكي يعرف Android الأنشطة المتاحة للإطلاق. تمكّن فئة النشاط تطبيقك من التفاعل مع تغييرات Android أيضًا، مثلاً عند دخول واجهة المستخدم في التطبيق أو مغادرتها الواجهة أو عند تدويرها، وما إلى ذلك. يمكن أن يكون النشاط بمثابة مكان لمشاركة الحالة بين الشاشات.
في سياق تطبيقك، يجب أن تعمل الأنشطة كمضيف للتنقل ويجب أن تتضمن المنطق والمعرفة حول كيفية الانتقال بين الشاشات وتمرير البيانات، وما إلى ذلك. ومع ذلك، من الأفضل إدارة تفاصيل واجهة المستخدم الخاصة بك في جزء أصغر قابل لإعادة الاستخدام من واجهة المستخدم الخاصة بك. التنفيذ المقترَح لهذا النمط هو الأجزاء. راجِع النشاط الأحادي: لماذا ومتى وكيف لمعرفة المزيد عن مزايا استخدام الأجزاء. يتيح التنقّل استخدام الأجزاء عبر تبعية جزء التنقّل. يدعم التنقل أيضًا أنواع الوجهات المخصّصة.
إذا كان تطبيقك لا يستخدم الأجزاء، عليك أولاً نقل كل شاشة في تطبيقك لاستخدام جزء. أنت لا تزيل النشاط في هذه المرحلة. بدلاً من ذلك، أنت تنشئ جزءًا لتمثيل الشاشة وفصل منطق واجهة المستخدم الخاص بك عن طريق المسئولية.
نقدّم لك ميزة الأجزاء
لتوضيح عملية تقديم الأجزاء، لنبدأ بمثال على تطبيق يتضمّن شاشتَين: شاشة قائمة المنتجات وشاشة تفاصيل المنتج. يؤدي النقر فوق منتج ما في شاشة القائمة إلى نقل المستخدم إلى شاشة التفاصيل لمعرفة المزيد عن المنتج.
في هذا المثال، تعد شاشات القائمة والتفاصيل أنشطة منفصلة حاليًا.
إنشاء تنسيق جديد لاستضافة واجهة المستخدم
لإضافة جزء، ابدأ بإنشاء ملف تنسيق جديد للنشاط لاستضافة الجزء. يحل ذلك محل تنسيق عرض المحتوى الحالي للنشاط.
لعرض بسيط، يمكنك استخدام FrameLayout
، كما هو موضّح في مثال product_list_host
التالي:
<FrameLayout
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/main_content"
android:layout_height="match_parent"
android:layout_width="match_parent" />
تشير السمة id
إلى قسم المحتوى الذي نضيف إليه الجزء لاحقًا.
بعد ذلك، في دالة onCreate()
الخاصة بنشاطك، عليك تعديل مرجع ملف التنسيق
في دالة onCreate لنشاطك التجاري للإشارة إلى ملف التنسيق الجديد هذا:
Kotlin
class ProductListActivity : AppCompatActivity() { ... override fun onCreate(savedInstanceState: Bundle?) { ... // Replace setContentView(R.layout.product_list) with the line below setContentView(R.layout.product_list_host) ... } }
Java
public class ProductListActivity extends AppCompatActivity { ... @Override public void onCreate(@Nullable Bundle savedInstanceState) { ... // Replace setContentView(R.layout.product_list); with the line below setContentView(R.layout.product_list_host); ... } }
يُستخدم التنسيق الحالي (product_list
، في هذا المثال) كطريقة العرض الجذر
للجزء الذي تريد إنشاءه.
إنشاء جزء
يمكنك إنشاء جزء جديد لإدارة واجهة المستخدم لشاشتك. من الممارسات الجيدة أن تكون
متناسقًا مع اسم مضيف النشاط. يستخدم المقتطف أدناه السمة ProductListFragment
، على سبيل المثال:
Kotlin
class ProductListFragment : Fragment() { // Leave empty for now. }
Java
public class ProductListFragment extends Fragment { // Leave empty for now. }
نقل منطق النشاط إلى جزء
مع تحديد الجزء، تتمثل الخطوة التالية في نقل منطق واجهة المستخدم
لهذه الشاشة من النشاط إلى هذا الجزء الجديد. إذا كنت تأتي من بنية قائمة على النشاط، فمن المحتمل أن يكون لديك الكثير من منطق إنشاء طرق العرض التي يحدث في دالة onCreate()
لنشاطك.
في ما يلي مثال لشاشة تستند إلى النشاط ومنطق واجهة المستخدم التي نحتاج إلى نقلها:
Kotlin
class ProductListActivity : AppCompatActivity() { // Views and/or ViewDataBinding references, Adapters... private lateinit var productAdapter: ProductAdapter private lateinit var binding: ProductListActivityBinding ... // ViewModels, System Services, other Dependencies... private val viewModel: ProductListViewModel by viewModels() ... override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) // View initialization logic DataBindingUtil.setContentView(this, R.layout.product_list_activity) // Post view initialization logic // Connect adapters productAdapter = ProductAdapter(productClickCallback) binding.productsList.setAdapter(productAdapter) // Initialize view properties, set click listeners, etc. binding.productsSearchBtn.setOnClickListener {...} // Subscribe to state viewModel.products.observe(this, Observer { myProducts -> ... }) // ...and so on } ... }
Java
public class ProductListActivity extends AppCompatActivity { // Views and/or ViewDataBinding references, adapters... private ProductAdapter productAdapter; private ProductListActivityBinding binding; ... // ViewModels, system services, other dependencies... private ProductListViewModel viewModel; ... @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); // View initialization logic DataBindingUtil.setContentView(this, R.layout.product_list_activity); // Post view initialization logic // Connect adapters productAdapter = new ProductAdapter(productClickCallback); binding.productsList.setAdapter(productAdapter); // Initialize ViewModels and other dependencies ProductListViewModel viewModel = new ViewModelProvider(this).get(ProductListViewModel.java); // Initialize view properties, set click listeners, etc. binding.productsSearchBtn.setOnClickListener(v -> { ... }); // Subscribe to state viewModel.getProducts().observe(this, myProducts -> ... ); // ...and so on }
قد يتحكم نشاطك أيضًا في وقت وكيفية انتقال المستخدم إلى الشاشة التالية، كما هو موضح في المثال التالي:
Kotlin
// Provided to ProductAdapter in ProductListActivity snippet. private val productClickCallback = ProductClickCallback { product -> show(product) } fun show(product: Product) { val intent = Intent(this, ProductActivity::class.java) intent.putExtra(ProductActivity.KEY_PRODUCT_ID, product.id) startActivity(intent) }
Java
// Provided to ProductAdapter in ProductListActivity snippet. private ProductClickCallback productClickCallback = this::show; private void show(Product product) { Intent intent = new Intent(this, ProductActivity.class); intent.putExtra(ProductActivity.KEY_PRODUCT_ID, product.getId()); startActivity(intent); }
داخل الجزء، يمكنك توزيع هذا العمل بين
onCreateView()
وonViewCreated()
،
مع إبقاء منطق التنقّل فقط في النشاط:
Kotlin
class ProductListFragment : Fragment() { private lateinit var binding: ProductListFragmentBinding private val viewModel: ProductListViewModel by viewModels() // View initialization logic override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { binding = DataBindingUtil.inflate( inflater, R.layout.product_list, container, false ) return binding.root } // Post view initialization logic override fun onViewCreated(view: View, savedInstanceState: Bundle?) { // Connect adapters productAdapter = ProductAdapter(productClickCallback) binding.productsList.setAdapter(productAdapter) // Initialize view properties, set click listeners, etc. binding.productsSearchBtn.setOnClickListener {...} // Subscribe to state viewModel.products.observe(this, Observer { myProducts -> ... }) // ...and so on } // Provided to ProductAdapter private val productClickCallback = ProductClickCallback { product -> if (lifecycle.currentState.isAtLeast(Lifecycle.State.STARTED)) { (requireActivity() as ProductListActivity).show(product) } } ... }
Java
public class ProductListFragment extends Fragment { private ProductAdapter productAdapter; private ProductListFragmentBinding binding; // View initialization logic @Nullable @Override public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { binding = DataBindingUtil.inflate( inflater, R.layout.product_list_fragment, container, false); return binding.getRoot(); } // Post view initialization logic @Override public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { // Connect adapters binding.productsList.setAdapter(productAdapter); // Initialize ViewModels and other dependencies ProductListViewModel viewModel = new ViewModelProvider(this) .get(ProductListViewModel.class); // Initialize view properties, set click listeners, etc. binding.productsSearchBtn.setOnClickListener(...) // Subscribe to state viewModel.getProducts().observe(this, myProducts -> { ... }); // ...and so on // Provided to ProductAdapter private ProductClickCallback productClickCallback = new ProductClickCallback() { @Override public void onClick(Product product) { if (getLifecycle().getCurrentState().isAtLeast(Lifecycle.State.STARTED)) { ((ProductListActivity) requireActivity()).show(product); } } }; ... }
في ProductListFragment
، لاحظ أنّه ما مِن دعوة إلى
setContentView()
تضخيم التنسيق وربطه. في أحد الأجزاء، يؤدي onCreateView()
إلى إعداد
طريقة العرض الجذر تأخذ onCreateView()
مثيلاً من
LayoutInflater
الذي يمكن استخدامه
لتكبير طريقة العرض الجذر استنادًا إلى ملف موارد تنسيق. يُعيد هذا المثال استخدام تنسيق product_list
الحالي الذي استخدمه النشاط لأنّه لا يلزم إجراء أي تغيير على التنسيق نفسه.
إذا كانت هناك أيّ منطق واجهة مستخدم مخزَّنة في وظائف onStart()
أو onResume()
أو
onPause()
أو onStop()
الخاصة بنشاطك ولا تتعلق بالتنقّل، يمكنك نقلها إلى الدوال المقابلة التي تحمل الاسم نفسه على الجزء.
تهيئة الجزء في النشاط المضيف
بمجرد نقل منطق كل واجهة المستخدم إلى الجزء، ينبغي أن يظل منطق التنقل فقط في النشاط.
Kotlin
class ProductListActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.product_list_host) } fun show(product: Product) { val intent = Intent(this, ProductActivity::class.java) intent.putExtra(ProductActivity.KEY_PRODUCT_ID, product.id) startActivity(intent) } }
Java
public class ProductListActivity extends AppCompatActivity { @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.product_list_host); } public void show(Product product) { Intent intent = new Intent(this, ProductActivity.class); intent.putExtra(ProductActivity.KEY_PRODUCT_ID, product.getId()); startActivity(intent); } }
الخطوة الأخيرة هي إنشاء مثيل للجزء في onCreate()
بعد ضبط طريقة عرض المحتوى مباشرةً:
Kotlin
override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.product_list_host) if (savedInstanceState == null) { val fragment = ProductListFragment() supportFragmentManager .beginTransaction() .add(R.id.main_content, fragment) .commit() } }
Java
@Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.product_list_host); if (savedInstanceState == null) { ProductListFragment fragment = new ProductListFragment(); getSupportFragmentManager() .beginTransaction() .add(R.id.main_content, fragment) .commit(); } }
كما هو موضّح في هذا المثال، تحفظ السمة FragmentManager
الأجزاء وتستعيدها تلقائيًا
بعد تغييرات الإعدادات، لذا ما عليك سوى إضافة الجزء إذا كانت
savedInstanceState
فارغة.
تمرير عناصر intent الإضافية إلى الجزء
إذا كان نشاطك يتلقّى دالة Extras
من خلال هدف، يمكنك تمريرها إلى الكسر مباشرةً كوسيطات.
في هذا المثال، تتلقى السمة ProductDetailsFragment
وسيطاتها مباشرةً من الإضافات الأساسية للنشاط:
Kotlin
... if (savedInstanceState == null) { val fragment = ProductDetailsFragment() // Intent extras and Fragment Args are both of type android.os.Bundle. fragment.arguments = intent.extras supportFragmentManager .beginTransaction() .add(R.id.main_content, fragment) .commit() } ...
Java
... if (savedInstanceState == null) { ProductDetailsFragment fragment = new ProductDetailsFragment(); // Intent extras and fragment Args are both of type android.os.Bundle. fragment.setArguments(getIntent().getExtras()); getSupportFragmentManager() .beginTransaction() .add(R.id.main_content, fragment) .commit(); } ...
في هذه المرحلة، يجب أن تتمكن من اختبار تشغيل تطبيقك مع تحديث الشاشة الأولى لاستخدام جزء. استمر في ترحيل بقية الشاشات القائمة على النشاط، مع قضاء بعض الوقت للاختبار بعد كل تكرار.
دمج مكوِّن التنقّل
بعد استخدام بنية قائمة على الأجزاء، تكون جاهزًا لبدء دمج مكون التنقل.
أولاً، أضف أحدث تبعيات التنقل إلى مشروعك، باتباع التعليمات الواردة في ملاحظات إصدار مكتبة التنقل.
إنشاء رسم بياني للتنقل
ويمثل مكوِّن التنقل إعدادات التنقل لتطبيقك في ملف مورد كرسم بياني، ويشبه إلى حد كبير تمثيل طرق عرض تطبيقك. يساعد هذا في إبقاء التنقل في تطبيقك منظّمًا خارج قاعدة الرموز ويوفر لك طريقة لتعديل التنقل في التطبيق بشكل مرئي.
لإنشاء رسم بياني للتنقّل، ابدأ بإنشاء مجلد موارد جديد باسم
navigation
. لإضافة الرسم البياني، انقر بزر الماوس الأيمن على هذا الدليل، واختر
جديد > ملف موارد التنقل.
يستخدم مكوِّن التنقل نشاطًا باعتباره
مضيف للتنقل
ويستبدل الأجزاء الفردية في هذا المضيف أثناء تنقل المستخدمين عبر
تطبيقك. قبل أن تتمكن من البدء في تخطيط تنقل تطبيقك مرئيًا،
تحتاج إلى ضبط NavHost
داخل النشاط الذي سيستضيف هذا
الرسم البياني. نظرًا لأننا نستخدم الأجزاء، يمكننا استخدام تنفيذ NavHost
التلقائي
NavHostFragment
في مكوِّن التنقل.
يتم ضبط NavHostFragment
من خلال عنصر FragmentContainerView
يتم وضعه داخل نشاط مضيف، كما هو موضّح في المثال التالي:
<androidx.fragment.app.FragmentContainerView
android:name="androidx.navigation.fragment.NavHostFragment"
app:navGraph="@navigation/product_list_graph"
app:defaultNavHost="true"
android:id="@+id/main_content"
android:layout_width="match_parent"
android:layout_height="match_parent" />
تشير السمة app:NavGraph
إلى الرسم البياني للتنقّل المرتبط بمضيف التنقّل هذا. يؤدي ضبط هذه الخاصية إلى تضخيم الرسم البياني للتنقل وتعيين خاصية الرسم البياني على NavHostFragment
. تضمن السمة app:defaultNavHost
أنّ جهاز NavHostFragment
يعترض زر الرجوع في النظام.
إذا كنت تستخدم التنقّل بمستوى أعلى مثل DrawerLayout
أو
BottomNavigationView
، سيحلّ هذا FragmentContainerView
محل عنصر عرض المحتوى الرئيسي. راجع
تحديث مكونات واجهة المستخدم باستخدام NavigationUI
للحصول على أمثلة.
للحصول على تنسيق بسيط، يمكنك تضمين عنصر FragmentContainerView
هذا
كعنصر ثانوي للجذر ViewGroup
:
<FrameLayout
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_height="match_parent"
android:layout_width="match_parent">
<androidx.fragment.app.FragmentContainerView
android:id="@+id/main_content"
android:name="androidx.navigation.fragment.NavHostFragment"
app:navGraph="@navigation/product_list_graph"
app:defaultNavHost="true"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</FrameLayout>
إذا نقرت على علامة التبويب Design (التصميم) في الأسفل، من المفترض أن ترى رسمًا بيانيًا مشابهًا للرسم الموضح أدناه. في أعلى يمين الرسم البياني، ضمن
الوجهات، يمكنك الاطّلاع على مرجع إلى نشاط NavHost
في شكل
layout_name (resource_id)
.
انقر على زرّ الإضافة بالقرب من أعلى الصفحة لإضافة الأجزاء إلى هذا الرسم البياني.
يشير عنصر التنقل إلى الشاشات الفردية باسم الوجهات. يمكن أن تكون الوجهات أجزاءً أو أنشطة أو وجهات مخصّصة. يمكنك إضافة أي نوع من الوجهات إلى الرسم البياني، ولكن تجدر الإشارة إلى أنّ وجهات الأنشطة تُعتبر وجهات نهائية، لأنّه بعد الانتقال إلى وجهة نشاط، تعمل ضمن مضيف تنقّل ورسم بياني منفصلَين.
يشير مكوِّن التنقل إلى الطريقة التي ينتقل بها المستخدمون من وجهة إلى أخرى باعتبارها إجراءات. يمكن أن تصف الإجراءات أيضًا الرسوم المتحركة الانتقالية وسلوك البوب.
إزالة المعاملات المجزأة
الآن وبعد استخدام مكوِّن التنقّل، إذا كنت تتنقل بين الشاشات المستندة إلى الأجزاء ضمن النشاط نفسه، يمكنك إزالة تفاعلات
FragmentManager
.
إذا كان تطبيقك يستخدم أجزاءً متعددة ضمن النشاط نفسه أو عناصر التنقّل على المستوى الأعلى، مثل تنسيق اللائحة أو شريط التنقّل السفلي، من المحتمل أنك تستخدم FragmentManager
وFragmentTransactions
لإضافة أجزاء أو استبدالها في قسم المحتوى الرئيسي في واجهة المستخدم. ويمكن الآن استبدال هذا الجزء وتبسيطه باستخدام مكوِّن التنقل من خلال توفير إجراءات لربط الوجهات ضمن الرسم البياني ثم التنقل باستخدام NavController
.
في ما يلي بعض السيناريوهات التي قد تواجهها مع طريقة التعامل مع عملية نقل البيانات لكل سيناريو.
نشاط واحد يدير أجزاء متعددة
إذا كان لديك نشاط واحد يدير أجزاء متعدّدة، قد يبدو رمز نشاطك على النحو التالي:
Kotlin
class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) // Logic to load the starting destination // when the Activity is first created if (savedInstanceState == null) { val fragment = ProductListFragment() supportFragmentManager.beginTransaction() .add(R.id.fragment_container, fragment, ProductListFragment.TAG) .commit() } } // Logic to navigate the user to another destination. // This may include logic to initialize and set arguments on the destination // fragment or even transition animations between the fragments (not shown here). fun navigateToProductDetail(productId: String) { val fragment = new ProductDetailsFragment() val args = Bundle().apply { putInt(KEY_PRODUCT_ID, productId) } fragment.arguments = args supportFragmentManager.beginTransaction() .addToBackStack(ProductDetailsFragment.TAG) .replace(R.id.fragment_container, fragment, ProductDetailsFragment.TAG) .commit() } }
Java
public class MainActivity extends AppCompatActivity { @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // Logic to load the starting destination when the activity is first created. if (savedInstanceState == null) { val fragment = ProductListFragment() supportFragmentManager.beginTransaction() .add(R.id.fragment_container, fragment, ProductListFragment.TAG) .commit(); } } // Logic to navigate the user to another destination. // This may include logic to initialize and set arguments on the destination // fragment or even transition animations between the fragments (not shown here). public void navigateToProductDetail(String productId) { Fragment fragment = new ProductDetailsFragment(); Bundle args = new Bundle(); args.putInt(KEY_PRODUCT_ID, productId); fragment.setArguments(args); getSupportFragmentManager().beginTransaction() .addToBackStack(ProductDetailsFragment.TAG) .replace(R.id.fragment_container, fragment, ProductDetailsFragment.TAG) .commit(); } }
داخل وجهة المصدر، قد تستدعي دالة تنقل كرد على حدث ما، كما هو موضح أدناه:
Kotlin
class ProductListFragment : Fragment() { ... override fun onViewCreated(view: View, savedInstanceState: Bundle?) { // In this example a callback is passed to respond to an item clicked // in a RecyclerView productAdapter = ProductAdapter(productClickCallback) binding.productsList.setAdapter(productAdapter) } ... // The callback makes the call to the activity to make the transition. private val productClickCallback = ProductClickCallback { product -> (requireActivity() as MainActivity).navigateToProductDetail(product.id) } }
Java
public class ProductListFragment extends Fragment { ... @Override public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { // In this example a callback is passed to respond to an item clicked in a RecyclerView productAdapter = new ProductAdapter(productClickCallback); binding.productsList.setAdapter(productAdapter); } ... // The callback makes the call to the activity to make the transition. private ProductClickCallback productClickCallback = product -> ( ((MainActivity) requireActivity()).navigateToProductDetail(product.getId()) ); }
يمكن استبدال ذلك بتحديث الرسم البياني للتنقل لتعيين وجهة البدء والإجراءات لربط وجهاتك وتحديد الوسيطات عند الحاجة:
<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/product_list_graph"
app:startDestination="@id/product_list">
<fragment
android:id="@+id/product_list"
android:name="com.example.android.persistence.ui.ProductListFragment"
android:label="Product List"
tools:layout="@layout/product_list">
<action
android:id="@+id/navigate_to_product_detail"
app:destination="@id/product_detail" />
</fragment>
<fragment
android:id="@+id/product_detail"
android:name="com.example.android.persistence.ui.ProductDetailFragment"
android:label="Product Detail"
tools:layout="@layout/product_detail">
<argument
android:name="product_id"
app:argType="integer" />
</fragment>
</navigation>
بعد ذلك، يمكنك تحديث نشاطك:
Kotlin
class MainActivity : AppCompatActivity() { // No need to load the start destination, handled automatically by the Navigation component override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) } }
Java
public class MainActivity extends AppCompatActivity { // No need to load the start destination, handled automatically by the Navigation component @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); } }
لم يعُد النشاط بحاجة إلى طريقة navigateToProductDetail()
. في القسم التالي، نعدّل السمة ProductListFragment
لاستخدام NavController
للانتقال إلى شاشة تفاصيل المنتج التالية.
تمرير الوسيطات بأمان
يحتوي مكوِّن التنقل على مكوّن Gradle الإضافي يسمى Safe Args، وهو ينشئ فئات بسيطة للكائنات والمنشئين من أجل الوصول الآمن من النوع إلى الوسيطات المحددة للوجهات والإجراءات.
بعد تطبيق المكوِّن الإضافي، تؤدي أي وسيطات معرّفة في وجهة في الرسم البياني للتنقّل إلى إنشاء إطار عمل مكوِّن التنقل
لفئة Arguments
توفر وسيطات من النوع الآمن للوجهة المستهدفة.
يؤدي تحديد إجراء إلى إنشاء المكوّن الإضافي لفئة ضبط Directions
يمكن استخدامها لإخبار NavController
بكيفية انتقال المستخدم إلى الوجهة المستهدفة. عندما يشير إجراء إلى وجهة تتطلّب وسيطات، تتضمّن فئة Directions
التي تم إنشاؤها طرق الدالة الإنشائية التي تتطلب هذه المعلَمات.
داخل الجزء، استخدِم NavController
وفئة Directions
التي تم إنشاؤها لتوفير وسيطات آمنة للنوع في الوجهة المستهدفة، كما هو موضّح في المثال التالي:
Kotlin
class ProductListFragment : Fragment() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { // In this example a callback is passed to respond to an item clicked in a RecyclerView productAdapter = ProductAdapter(productClickCallback) binding.productsList.setAdapter(productAdapter) } ... // The callback makes the call to the NavController to make the transition. private val productClickCallback = ProductClickCallback { product -> val directions = ProductListDirections.navigateToProductDetail(product.id) findNavController().navigate(directions) } }
Java
public class ProductListFragment extends Fragment { ... @Override public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { // In this example a callback is passed to respond to an item clicked in a RecyclerView productAdapter = new ProductAdapter(productClickCallback); binding.productsList.setAdapter(productAdapter); } ... // The callback makes the call to the activity to make the transition. private ProductClickCallback productClickCallback = product -> { ProductListDirections.ViewProductDetails directions = ProductListDirections.navigateToProductDetail(product.getId()); NavHostFragment.findNavController(this).navigate(directions); }; }
التنقل على المستوى الأعلى
إذا كان تطبيقك يستخدم DrawerLayout
، قد يتوفّر لديك الكثير من أساليب الضبط
في نشاطك التي تدير فتح اللائحة وإغلاقها والانتقال إلى
وجهات أخرى.
قد يبدو النشاط الناتج كما يلي:
Kotlin
class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelectedListener { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) val toolbar: Toolbar = findViewById(R.id.toolbar) setSupportActionBar(toolbar) val drawerLayout: DrawerLayout = findViewById(R.id.drawer_layout) val navView: NavigationView = findViewById(R.id.nav_view) val toggle = ActionBarDrawerToggle( this, drawerLayout, toolbar, R.string.navigation_drawer_open, R.string.navigation_drawer_close ) drawerLayout.addDrawerListener(toggle) toggle.syncState() navView.setNavigationItemSelectedListener(this) } override fun onBackPressed() { val drawerLayout: DrawerLayout = findViewById(R.id.drawer_layout) if (drawerLayout.isDrawerOpen(GravityCompat.START)) { drawerLayout.closeDrawer(GravityCompat.START) } else { super.onBackPressed() } } override fun onNavigationItemSelected(item: MenuItem): Boolean { // Handle navigation view item clicks here. when (item.itemId) { R.id.home -> { val homeFragment = HomeFragment() show(homeFragment) } R.id.gallery -> { val galleryFragment = GalleryFragment() show(galleryFragment) } R.id.slide_show -> { val slideShowFragment = SlideShowFragment() show(slideShowFragment) } R.id.tools -> { val toolsFragment = ToolsFragment() show(toolsFragment) } } val drawerLayout: DrawerLayout = findViewById(R.id.drawer_layout) drawerLayout.closeDrawer(GravityCompat.START) return true } } private fun show(fragment: Fragment) { val drawerLayout = drawer_layout as DrawerLayout val fragmentManager = supportFragmentManager fragmentManager .beginTransaction() .replace(R.id.main_content, fragment) .commit() drawerLayout.closeDrawer(GravityCompat.START) }
Java
public class MainActivity extends AppCompatActivity implements NavigationView.OnNavigationItemSelectedListener { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Toolbar toolbar = findViewById(R.id.toolbar); setSupportActionBar(toolbar); DrawerLayout drawer = findViewById(R.id.drawer_layout); NavigationView navigationView = findViewById(R.id.nav_view); ActionBarDrawerToggle toggle = new ActionBarDrawerToggle( this, drawer, toolbar, R.string.navigation_drawer_open, R.string.navigation_drawer_close); drawer.addDrawerListener(toggle); toggle.syncState(); navigationView.setNavigationItemSelectedListener(this); } @Override public void onBackPressed() { DrawerLayout drawer = findViewById(R.id.drawer_layout); if (drawer.isDrawerOpen(GravityCompat.START)) { drawer.closeDrawer(GravityCompat.START); } else { super.onBackPressed(); } } @Override public boolean onNavigationItemSelected(MenuItem item) { // Handle navigation view item clicks here. int id = item.getItemId(); if (id == R.id.home) { Fragment homeFragment = new HomeFragment(); show(homeFragment); } else if (id == R.id.gallery) { Fragment galleryFragment = new GalleryFragment(); show(galleryFragment); } else if (id == R.id.slide_show) { Fragment slideShowFragment = new SlideShowFragment(); show(slideShowFragment); } else if (id == R.id.tools) { Fragment toolsFragment = new ToolsFragment(); show(toolsFragment); } DrawerLayout drawer = findViewById(R.id.drawer_layout); drawer.closeDrawer(GravityCompat.START); return true; } private void show(Fragment fragment) { DrawerLayout drawerLayout = findViewById(R.id.drawer_layout); FragmentManager fragmentManager = getSupportFragmentManager(); fragmentManager .beginTransaction() .replace(R.id.main_content, fragment) .commit(); drawerLayout.closeDrawer(GravityCompat.START); } }
بعد إضافة مكوِّن التنقل إلى مشروعك وإنشاء رسم بياني للتنقل، أضف كل وجهات المحتوى من الرسم البياني (مثل
الصفحة الرئيسية والمعرض وSlideShow والأدوات من المثال أعلاه). تأكَّد من أنّ قيم عنصر القائمة id
تتطابق مع قيم id
للوجهة المرتبطة بها،
كما هو موضّح أدناه:
<!-- activity_main_drawer.xml -->
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
tools:showIn="navigation_view">
<group android:checkableBehavior="single">
<item
android:id="@+id/home"
android:icon="@drawable/ic_menu_camera"
android:title="@string/menu_home" />
<item
android:id="@+id/gallery"
android:icon="@drawable/ic_menu_gallery"
android:title="@string/menu_gallery" />
<item
android:id="@+id/slide_show"
android:icon="@drawable/ic_menu_slideshow"
android:title="@string/menu_slideshow" />
<item
android:id="@+id/tools"
android:icon="@drawable/ic_menu_manage"
android:title="@string/menu_tools" />
</group>
</menu>
<!-- activity_main_graph.xml -->
<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/main_graph"
app:startDestination="@id/home">
<fragment
android:id="@+id/home"
android:name="com.example.HomeFragment"
android:label="Home"
tools:layout="@layout/home" />
<fragment
android:id="@+id/gallery"
android:name="com.example.GalleryFragment"
android:label="Gallery"
tools:layout="@layout/gallery" />
<fragment
android:id="@+id/slide_show"
android:name="com.example.SlideShowFragment"
android:label="Slide Show"
tools:layout="@layout/slide_show" />
<fragment
android:id="@+id/tools"
android:name="com.example.ToolsFragment"
android:label="Tools"
tools:layout="@layout/tools" />
</navigation>
إذا طابقت قيم id
من القائمة والرسم البياني، يمكنك اختيار نقل البيانات من
NavController
إلى هذا النشاط من أجل معالجة التنقّل تلقائيًا استنادًا إلى
عنصر في القائمة. تعالج NavController
أيضًا فتح وإغلاق DrawerLayout
والتعامل مع سلوك زر "السهم المتّجه للأعلى" و"الرجوع" بشكل مناسب.
يمكن بعد ذلك تحديث "MainActivity
" لتوصيل NavController
إلى
Toolbar
وNavigationView
.
للاطّلاع على مثال، يمكنك الاطّلاع على المقتطف التالي:
Kotlin
class MainActivity : AppCompatActivity() { val drawerLayout by lazy { findViewById<DrawerLayout>(R.id.drawer_layout) } val navController by lazy { (supportFragmentManager.findFragmentById(R.id.main_content) as NavHostFragment).navController } val navigationView by lazy { findViewById<NavigationView>(R.id.nav_view) } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) val toolbar = findViewById<Toolbar>(R.id.toolbar) setSupportActionBar(toolbar) // Show and Manage the Drawer and Back Icon setupActionBarWithNavController(navController, drawerLayout) // Handle Navigation item clicks // This works with no further action on your part if the menu and destination id’s match. navigationView.setupWithNavController(navController) } override fun onSupportNavigateUp(): Boolean { // Allows NavigationUI to support proper up navigation or the drawer layout // drawer menu, depending on the situation return navController.navigateUp(drawerLayout) } }
Java
public class MainActivity extends AppCompatActivity { private DrawerLayout drawerLayout; private NavController navController; private NavigationView navigationView; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); drawerLayout = findViewById(R.id.drawer_layout); NavHostFragment navHostFragment = (NavHostFragment) getSupportFragmentManager().findFragmentById(R.id.main_content); navController = navHostFragment.getNavController(); navigationView = findViewById(R.id.nav_view); Toolbar toolbar = findViewById(R.id.toolbar); setSupportActionBar(toolbar); // Show and Manage the Drawer and Back Icon NavigationUI.setupActionBarWithNavController(this, navController, drawerLayout); // Handle Navigation item clicks // This works with no further action on your part if the menu and destination id’s match. NavigationUI.setupWithNavController(navigationView, navController); } @Override public boolean onSupportNavigateUp() { // Allows NavigationUI to support proper up navigation or the drawer layout // drawer menu, depending on the situation. return NavigationUI.navigateUp(navController, drawerLayout); } }
يمكنك استخدام هذه الطريقة نفسها مع كلٍّ من التنقل القائم على Bottom NavigationView والتنقل القائم على القائمة. راجع تحديث مكونات واجهة المستخدم باستخدام NavigationUI للحصول على مزيد من الأمثلة.
إضافة وجهات النشاط
بعد توصيل كل شاشة في تطبيقك بالشبكة لاستخدام مكوِّن التنقل وعدم استخدام FragmentTransactions
للانتقال بين الوجهات المستندة إلى الأجزاء، تتمثل الخطوة التالية في استبعاد طلبات startActivity
.
أولاً، حدِّد الأماكن التي يتوفّر فيها رسمان بيانيان منفصلان للتنقّل في تطبيقك وتستخدم startActivity
للانتقال بينهما.
يحتوي هذا المثال على رسمين بيانيين (A وB) واستدعاء startActivity()
للانتقال من A إلى B.
Kotlin
fun navigateToProductDetails(productId: String) { val intent = Intent(this, ProductDetailsActivity::class.java) intent.putExtra(KEY_PRODUCT_ID, productId) startActivity(intent) }
Java
private void navigateToProductDetails(String productId) { Intent intent = new Intent(this, ProductDetailsActivity.class); intent.putExtra(KEY_PRODUCT_ID, productId); startActivity(intent);
بعد ذلك، استبدِلها بوجهة نشاط في الرسم البياني أ التي تمثل الانتقال إلى نشاط المضيف للرسم البياني ب. إذا كان لديك وسيطات تمريرها إلى وجهة بداية الرسم البياني ب، يمكنك تعيينها في تعريف وجهة النشاط.
في المثال التالي، يحدّد الرسم البياني "أ" وجهة نشاط تأخذ وسيطة product_id
مع إجراء. لا يحتوي الرسم البياني ب على أي تغييرات.
قد يبدو تمثيل XML للرسمين البيانيين A وB على النحو التالي:
<!-- Graph A -->
<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/product_list_graph"
app:startDestination="@id/product_list">
<fragment
android:id="@+id/product_list"
android:name="com.example.android.persistence.ui.ProductListFragment"
android:label="Product List"
tools:layout="@layout/product_list_fragment">
<action
android:id="@+id/navigate_to_product_detail"
app:destination="@id/product_details_activity" />
</fragment>
<activity
android:id="@+id/product_details_activity"
android:name="com.example.android.persistence.ui.ProductDetailsActivity"
android:label="Product Details"
tools:layout="@layout/product_details_host">
<argument
android:name="product_id"
app:argType="integer" />
</activity>
</navigation>
<!-- Graph B -->
<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"
app:startDestination="@id/product_details">
<fragment
android:id="@+id/product_details"
android:name="com.example.android.persistence.ui.ProductDetailsFragment"
android:label="Product Details"
tools:layout="@layout/product_details_fragment">
<argument
android:name="product_id"
app:argType="integer" />
</fragment>
</navigation>
يمكنك الانتقال إلى نشاط المضيف للرسم البياني ب باستخدام الآليات نفسها التي تستخدمها للانتقال إلى وجهات التقسيم:
Kotlin
fun navigateToProductDetails(productId: String) { val directions = ProductListDirections.navigateToProductDetail(productId) findNavController().navigate(directions) }
Java
private void navigateToProductDetails(String productId) { ProductListDirections.NavigateToProductDetail directions = ProductListDirections.navigateToProductDetail(productId); Navigation.findNavController(getView()).navigate(directions);
تمرير وسيطات وجهة النشاط إلى جزء وجهة البداية
إذا تلقّى نشاط الوجهة إضافات، كما في المثال السابق،
يمكنك تمريرها إلى وجهة البداية مباشرةً كوسيطات، ولكن عليك
ضبط الرسم البياني للتنقل الخاص بمضيفك يدويًا داخل طريقة
onCreate()
الخاصة بنشاط المضيف كي تتمكّن من تمرير المكوّنات الإضافية كوسيطات إلى الجزء، كما هو موضّح أدناه:
Kotlin
class ProductDetailsActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.product_details_host) val navHostFragment = supportFragmentManager.findFragmentById(R.id.main_content) as NavHostFragment val navController = navHostFramgent.navController navController .setGraph(R.navigation.product_detail_graph, intent.extras) } }
Java
public class ProductDetailsActivity extends AppCompatActivity { @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.product_details_host); NavHostFragment navHostFragment = (NavHostFragment) getSupportFragmentManager().findFragmentById(R.id.main_content); NavController navController = navHostFragment.getNavController(); navController .setGraph(R.navigation.product_detail_graph, getIntent().getExtras()); } }
يمكن سحب البيانات من وسيطات التجزئة Bundle
باستخدام فئة الوسيطات التي تم إنشاؤها، كما هو موضّح في المثال التالي:
Kotlin
class ProductDetailsFragment : Fragment() { val args by navArgs<ProductDetailsArgs>() override fun onViewCreated(view: View, savedInstanceState: Bundle?) { val productId = args.productId ... } ...
Java
public class ProductDetailsFragment extends Fragment { ProductDetailsArgs args; @Override public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); args = ProductDetailsArgs.fromBundle(requireArguments()); } @Override public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { int productId = args.getProductId(); ... } ...
دمج الأنشطة
يمكنك الجمع بين الرسوم البيانية للتنقّل في الحالات التي تتشارك فيها أنشطة متعدّدة
التنسيق نفسه، مثل عنصر FrameLayout
بسيط يحتوي على جزء واحد. في معظم هذه الحالات، يمكنك فقط دمج جميع العناصر من كل رسم بياني للتنقّل وتعديل أي عناصر وجهة نشاط إلى وجهات للأجزاء.
يجمع المثال التالي بين الرسمين البيانيين A وB من القسم السابق:
قبل الدمج:
<!-- Graph A -->
<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/product_list_graph"
app:startDestination="@id/product_list">
<fragment
android:id="@+id/product_list"
android:name="com.example.android.persistence.ui.ProductListFragment"
android:label="Product List Fragment"
tools:layout="@layout/product_list">
<action
android:id="@+id/navigate_to_product_detail"
app:destination="@id/product_details_activity" />
</fragment>
<activity
android:id="@+id/product_details_activity"
android:name="com.example.android.persistence.ui.ProductDetailsActivity"
android:label="Product Details Host"
tools:layout="@layout/product_details_host">
<argument android:name="product_id"
app:argType="integer" />
</activity>
</navigation>
<!-- Graph B -->
<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/product_detail_graph"
app:startDestination="@id/product_details">
<fragment
android:id="@+id/product_details"
android:name="com.example.android.persistence.ui.ProductDetailsFragment"
android:label="Product Details"
tools:layout="@layout/product_details">
<argument
android:name="product_id"
app:argType="integer" />
</fragment>
</navigation>
بعد الدمج:
<!-- Combined Graph A and B -->
<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/product_list_graph"
app:startDestination="@id/product_list">
<fragment
android:id="@+id/product_list"
android:name="com.example.android.persistence.ui.ProductListFragment"
android:label="Product List Fragment"
tools:layout="@layout/product_list">
<action
android:id="@+id/navigate_to_product_detail"
app:destination="@id/product_details" />
</fragment>
<fragment
android:id="@+id/product_details"
android:name="com.example.android.persistence.ui.ProductDetailsFragment"
android:label="Product Details"
tools:layout="@layout/product_details">
<argument
android:name="product_id"
app:argType="integer" />
</fragment>
</navigation>
إنّ الإبقاء على أسماء الإجراءات كما هي أثناء الدمج قد يجعل هذه العملية سلسة، وبدون الحاجة إلى إجراء تغييرات على الرموز البرمجية الحالية. على سبيل المثال،
تظل navigateToProductDetail
كما هي هنا. الفرق الوحيد هو أنّ
هذا الإجراء يمثّل الآن التنقّل إلى وجهة جزء ضمن علامة NavHost
نفسها بدلاً من وجهة نشاط معيّنة:
Kotlin
fun navigateToProductDetails(productId: String) { val directions = ProductListDirections.navigateToProductDetail(productId) findNavController().navigate(directions) }
Java
private void navigateToProductDetails(String productId) { ProductListDirections.NavigateToProductDetail directions = ProductListDirections.navigateToProductDetail(productId); Navigation.findNavController(getView()).navigate(directions);
مراجع إضافية
لمزيد من المعلومات المتعلقة بالتنقل، راجع الموضوعات التالية:
- تحديث مكونات واجهة المستخدم باستخدام NavigationUI - تعرّف على كيفية إدارة التنقل باستخدام شريط التطبيق العلوي ودرج التنقل والتنقل السفلي
- اختبار التنقل - تعرَّف على كيفية اختبار سير عمل التنقل لتطبيقك