مكوِّن التنقل هو يمكنها إدارة التنقل المعقد وحركة الانتقال والربط بموضع معين ووسيطة وقت التجميع التي تم فحصها بين الشاشات في تطبيقك.
يُستخدم هذا المستند كدليل للأغراض العامة لنقل بيانات تطبيق حالي إلى استخدام مكوِّن التنقل.
على مستوى عالٍ، تتضمن عملية نقل البيانات الخطوات التالية:
نقل منطق واجهة المستخدم الخاصة بشاشة معيّنة من الأنشطة: نقل واجهة مستخدم تطبيقك منطقية خارج الأنشطة، مما يضمن أن كل نشاط يمتلك منطق مكونات واجهة المستخدم للتنقل العام، مثل
Toolbar
، مع تفويض تنفيذ كل شاشة بجزء أو وجهة مخصصة.دمج مكوِّن التنقل - لكل نشاط، أنشِئ رسم بياني للتنقل يحتوي على جزء واحد أو أكثر يديره ذلك الأخرى. استبدال المعاملات المجزأة بعمليات مكوِّن التنقل.
إضافة وجهات نشاط - استبدال
startActivity()
مكالمة بـ الإجراءات باستخدام وجهات الأنشطة.دمج الأنشطة - دمج الرسوم البيانية للتنقل في الحالات التي تشترك أنشطة متعددة في تخطيط مشترك.
المتطلّبات الأساسية
يفترض هذا الدليل أنك سبق أن نقلت تطبيقك لاستخدامه. مكتبات AndroidX. إذا لم تكن قد قمت بذلك، يمكنك نقل مشروعك لاستخدام AndroidX قبل المتابعة.
نقل منطق واجهة المستخدم الخاص بشاشة خارج الأنشطة
الأنشطة هي مكونات على مستوى النظام تُسهل التفاعل الرسومي بين تطبيقك وAndroid. تم تسجيل الأنشطة في بيان التطبيق. كي يعرف Android الأنشطة المتاحة للإطلاق. النشاط الفئة تطبيقك من التفاعل مع تغييرات Android أيضًا، مثلاً عند دخول واجهة مستخدم التطبيق إلى المقدمة أو مغادرتها والتدوير وما إلى ذلك. تشير رسالة الأشكال البيانية يمكن أن يكون نشاطك أيضًا بمثابة مكان مشاركة الحالة بين الشاشات.
في سياق تطبيقك، يجب أن تكون الأنشطة بمثابة مضيف للتنقل ويجب أن يمتلك المنطق والمعرفة بكيفية الانتقال بين الشاشات، وتمرير البيانات وما إلى ذلك. ومع ذلك، من الأفضل إدارة تفاصيل واجهة المستخدم إلى جزء أصغر وقابل لإعادة الاستخدام من واجهة المستخدم. التنفيذ الموصى به لهذا النمط هو أجزاء. عرض النشاط الواحد: لماذا ومتى وكيف لمعرفة المزيد عن مزايا استخدام الأجزاء. يتوافق التنقل مع الأجزاء من خلال الاعتمادية جزء التنقل. يدعم التنقل أيضًا أنواع الوجهات المخصّصة.
إذا كان تطبيقك لا يستخدم الأجزاء، عليك أولاً نقل البيانات. كل شاشة في التطبيق لاستخدام جزء. أنت لا تزيل النشاط في هذه النقطة. بل بدلاً من ذلك، أنت تنشئ جزءًا لتمثيل الشاشة والفاصل تمييز منطق واجهة المستخدم لديك حسب المسئولية.
مقدمة عن الأجزاء
لتوضيح عملية إدخال الأجزاء، لنبدأ بمثال لتطبيق يتألف من شاشتَين: شاشة قائمة المنتجات product details (تفاصيل المنتج). يؤدي النقر على أحد المنتجات في شاشة القائمة إلى الانتقال المستخدم إلى شاشة التفاصيل لمعرفة المزيد عن المنتج.
في هذا المثال، تعد شاشتا القائمة والتفاصيل أنشطة منفصلة حاليًا.
إنشاء تنسيق جديد لاستضافة واجهة المستخدم
لتقديم جزء، ابدأ بإنشاء ملف تخطيط جديد للنشاط لاستضافة الجزء. يحل ذلك محل تنسيق عرض المحتوى الحالي للنشاط.
للحصول على عرض بسيط، يمكنك استخدام 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>
إذا نقرت على علامة التبويب تصميم في الجزء السفلي، سيظهر لك رسم بياني مشابه
إلى القائمة الموضحة أدناه. في أعلى الجانب الأيسر من الرسم البياني، أسفل
الوجهات، يمكنك الاطّلاع على إشارة إلى نشاط 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 يسمى الوسيطات الآمنة تُنشئ كائنات بسيطة وفئات إنشاءية للوصول الآمن إلى النوع الوسيطات المحددة للوجهات والإجراءات.
بعد تطبيق المكوّن الإضافي، سيتم تحديد أي وسيطات محددة على وجهة في
يتسبب الرسم البياني للتنقل في إطار عمل مكوِّن التنقل إلى إنشاء
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); } }
بعد إضافة مكون التنقل إلى مشروعك وإنشاء
الرسم البياني للتنقل، أضف كل وجهات المحتوى من الرسم البياني (مثل
الصفحة الرئيسية والمعرض وعرض الشرائح والأدوات من المثال أعلاه). التأكد من
أن تتطابق قيم عنصر القائمة 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()
للانتقال من "أ" إلى "ب".
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()
بحيث يمكنك تمرير عناصر intent الإضافية كوسيطات إلى
كما هو موضح أدناه:
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
باستخدام
فئة args التي تم إنشاؤها، كما هو موضّح في المثال التالي:
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
بسيط يحتوي على جزء واحد. ضِمن
في معظم هذه الحالات، يمكنك فقط دمج جميع العناصر من كل
الرسم البياني للتنقل وتعديل أي عناصر وجهة النشاط إلى تجزئة
المقصودة.
يجمع المثال التالي الرسمين البيانيين أ و ب من القسم السابق:
قبل الدمج:
<!-- 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 - تعرَّف على طريقة إدارة التنقّل باستخدام شريط التطبيقات العلوي ودرج التنقّل والتنقل السفلي
- اختبار التنقل - التعرُّف على كيفية اختبار سير عمل التنقّل لتطبيقك