نقل البيانات إلى مكوّن التنقّل

مكوِّن التنقل هو يمكنها إدارة التنقل المعقد وحركة الانتقال والربط بموضع معين ووسيطة وقت التجميع التي تم فحصها بين الشاشات في تطبيقك.

يُستخدم هذا المستند كدليل للأغراض العامة لنقل بيانات تطبيق حالي إلى استخدام مكوِّن التنقل.

على مستوى عالٍ، تتضمن عملية نقل البيانات الخطوات التالية:

  1. نقل منطق واجهة المستخدم الخاصة بشاشة معيّنة من الأنشطة: نقل واجهة مستخدم تطبيقك منطقية خارج الأنشطة، مما يضمن أن كل نشاط يمتلك منطق مكونات واجهة المستخدم للتنقل العام، مثل Toolbar، مع تفويض تنفيذ كل شاشة بجزء أو وجهة مخصصة.

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

  3. إضافة وجهات نشاط - استبدال startActivity() مكالمة بـ الإجراءات باستخدام وجهات الأنشطة.

  4. دمج الأنشطة - دمج الرسوم البيانية للتنقل في الحالات التي تشترك أنشطة متعددة في تخطيط مشترك.

المتطلّبات الأساسية

يفترض هذا الدليل أنك سبق أن نقلت تطبيقك لاستخدامه. مكتبات 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);

مراجع إضافية

للحصول على مزيد من المعلومات المتعلقة بالتنقل، اطّلِع على المواضيع التالية: