نقل البيانات من ViewPager إلى ViewPager2

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

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

مزايا نقل البيانات إلى ViewPager2

السبب الرئيسي لنقل البيانات هو تلقّي ViewPager2 لدعم التطوير النشط، بينما لا يتلقى ViewPager ذلك. على الرغم من ذلك، تقدّم السمة ViewPager2 أيضًا العديد من المزايا المحدّدة الأخرى.

إتاحة الاتجاه العمودي

تتيح ViewPager2 ميزة "التقسيم إلى صفحات عمودية" بالإضافة إلى تنسيق "التنقّل الأفقي" التقليدي. يمكنك تفعيل التقسيم على صفحات عمودية لعنصر ViewPager2 من خلال ضبط سمة android:orientation الخاصة به:

<androidx.viewpager2.widget.ViewPager2
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/pager"
    android:orientation="vertical" />

يمكنك أيضًا ضبط هذه السمة آليًا باستخدام الطريقة setOrientation().

دعم من اليمين إلى اليسار

يتيح ViewPager2 الانتقال إلى صفحات من اليمين إلى اليسار (من اليمين إلى اليسار). يتم تفعيل ميزة الانتقال من اليمين إلى اليسار تلقائيًا عندما يكون ذلك مناسبًا استنادًا إلى اللغة، ولكن يمكنك أيضًا تفعيل الانتقال من اليمين إلى اليسار للعنصر ViewPager2 يدويًا من خلال ضبط سمة android:layoutDirection الخاصة به:

<androidx.viewpager2.widget.ViewPager2
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/pager"
    android:layoutDirection="rtl" />

يمكنك أيضًا ضبط هذه السمة آليًا باستخدام طريقة setLayoutDirection().

مجموعات الأجزاء القابلة للتعديل

تتيح السمة ViewPager2 الانتقال إلى مجموعة من الأجزاء قابلة للتعديل، من خلال طلب notifyDatasetChanged() تعديل واجهة المستخدم عند تغيير المجموعة الأساسية.

وهذا يعني أنّه يمكن لتطبيقك تعديل مجموعة الأجزاء بشكل ديناميكي في وقت التشغيل، وسيعرض ViewPager2 المجموعة المعدّلة بشكل صحيح.

مختلف

تم تصميم "ViewPager2" على RecyclerView، ما يعني أنّه بإمكانه الوصول إلى فئة الأدوات المساعدة DiffUtil. وينتج عن ذلك عدة فوائد، ولكن أبرزها هو أنّ كائنات ViewPager2 تستفيد أصلاً من الرسوم المتحركة لتغيير مجموعة البيانات من الفئة RecyclerView.

نقل تطبيقك إلى ViewPager2

اتّبِع الخطوات التالية لتعديل ViewPager عنصرًا في تطبيقك إلى ViewPager2:

تعديل ملفات تنسيق XML

أولاً، استبدِل عناصر ViewPager في ملفات تنسيق XML بعناصر ViewPager2:

<!-- A ViewPager element -->
<android.support.v4.view.ViewPager
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/pager"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />

<!-- A ViewPager2 element -->
<androidx.viewpager2.widget.ViewPager2
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/pager"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />

تعديل فئات المحوِّلات

عند استخدام ViewPager، كان عليك توسيع فئة المحوِّل التي وفّرت صفحات جديدة إلى العنصر. استنادًا إلى حالة الاستخدام، استخدَم ViewPager ثلاث فئات مجردة مختلفة. يستخدم "ViewPager2" فئتين مجردة.

مع كل عنصر ViewPager يتم تحويله إلى كائن ViewPager2، عليك تعديل فئة المحوِّل لتوسيع فئة التجريد المناسبة على النحو التالي:

مَعلمات المنشئ

فئات المحوِّلات المستندة إلى التجزئة التي يتم اكتسابها من FragmentPagerAdapter أو FragmentStatePagerAdapter تقبل دائمًا كائن FragmentManager واحدًا كمَعلمة إنشائية. عند تمديد FragmentStateAdapter لفئة محوّل ViewPager2، تتوفّر لك الخيارات التالية لمَعلمات الدالة الإنشائية بدلاً من ذلك:

  • الكائن FragmentActivity أو الكائن Fragment الذي يتوفّر فيه الكائن ViewPager2. وفي معظم الحالات، يكون هذا هو الخيار الأفضل.
  • عنصر FragmentManager وكائن Lifecycle.

إنّ فئات المحوِّلات المستندة إلى الملف الشخصي التي تكتسب مباشرةً من RecyclerView.Adapter لا تتطلب معلَمة دالة إنشائية.

طُرق التجاهل

تحتاج فئات المحوِّل أيضًا إلى إلغاء الطرق المختلفة لـ ViewPager2 مقارنةً بالطرق المختلفة لـ ViewPager:

  • بدلاً من getCount()، يمكنك إلغاء getItemCount(). بخلاف الاسم، لم تتغير هذه الطريقة.
  • بدلاً من getItem()، يمكنك إلغاء createFragment() في فئات المعدِّلات المستندة إلى الأجزاء. تأكَّد من أنّ طريقة createFragment() الجديدة توفّر دائمًا مثيل جزء جديد في كل مرة يتم فيها استدعاء الدالة بدلاً من إعادة استخدام المثيلات.

ملخّص

باختصار، لتحويل فئة محوّل ViewPager لاستخدامها مع ViewPager2، يجب إجراء التغييرات التالية:

  1. يمكنك تغيير الفئة الرئيسية إلى RecyclerView.Adapter للتقسيم إلى صفحات بين المشاهدات أو إلى FragmentStateAdapter للتقسيم إلى صفحات بين الأجزاء.
  2. تغيير معلمات الدالة الإنشائية في فئات المحوِّلات المستندة إلى الأجزاء.
  3. يمكنك إلغاء getItemCount() بدلاً من getCount().
  4. يمكنك إلغاء createFragment() بدلاً من getItem() في فئات المحوِّلات المستندة إلى الأجزاء.

Kotlin

// A simple ViewPager adapter class for paging through fragments
class ScreenSlidePagerAdapter(fm: FragmentManager) : FragmentStatePagerAdapter(fm) {
    override fun getCount(): Int = NUM_PAGES

    override fun getItem(position: Int): Fragment = ScreenSlidePageFragment()
}

// An equivalent ViewPager2 adapter class
class ScreenSlidePagerAdapter(fa: FragmentActivity) : FragmentStateAdapter(fa) {
    override fun getItemCount(): Int = NUM_PAGES

    override fun createFragment(position: Int): Fragment = ScreenSlidePageFragment()
}

Java

// A simple ViewPager adapter class for paging through fragments
public class ScreenSlidePagerAdapter extends FragmentStatePagerAdapter {
    public ScreenSlidePagerAdapter(FragmentManager fm) {
        super(fm);
    }

    @Override
    public Fragment getItem(int position) {
        return new ScreenSlidePageFragment();
    }

    @Override
    public int getCount() {
        return NUM_PAGES;
    }
}

// An equivalent ViewPager2 adapter class
private class ScreenSlidePagerAdapter extends FragmentStateAdapter {
    public ScreenSlidePagerAdapter(FragmentActivity fa) {
        super(fa);
    }

    @Override
    public Fragment createFragment(int position) {
        return new ScreenSlidePageFragment();
    }

    @Override
    public int getItemCount() {
        return NUM_PAGES;
    }
}

إعادة هيكلة واجهات TabLayout

تم إدخال تغييرات على عملية دمج TabLayout من قِبل "ViewPager2". إذا كنت تستخدم حاليًا ViewPager مع كائن TabLayout لعرض علامات التبويب الأفقية للتنقل، عليك إعادة ضبط الكائن TabLayout لدمجه مع ViewPager2.

تم فصل TabLayout عن ViewPager2 وهو متاح الآن كجزء من مكوّنات Material. لاستخدامها، يجب إضافة التبعية المناسبة إلى ملف build.gradle:

رائع

implementation "com.google.android.material:material:1.1.0-beta01"

Kotlin

implementation("com.google.android.material:material:1.1.0-beta01")

عليك أيضًا تغيير موقع العنصر TabLayout في التسلسل الهرمي لملف تنسيق XML. مع ViewPager، يتم تعريف العنصر TabLayout كعنصر ثانوي للعنصر ViewPager، ولكن باستخدام ViewPager2، يتم تعريف العنصر TabLayout فوق العنصر ViewPager2 مباشرةً، على المستوى نفسه:

<!-- A ViewPager element with a TabLayout -->
<androidx.viewpager.widget.ViewPager
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/pager"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <com.google.android.material.tabs.TabLayout
        android:id="@+id/tab_layout"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

</androidx.viewpager.widget.ViewPager>

<!-- A ViewPager2 element with a TabLayout -->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <com.google.android.material.tabs.TabLayout
        android:id="@+id/tab_layout"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

    <androidx.viewpager2.widget.ViewPager2
        android:id="@+id/pager"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1" />

</LinearLayout>

أخيرًا، يجب تعديل الرمز الذي يرفق الكائن TabLayout بالكائن ViewPager. يستخدم TabLayout طريقة setupWithViewPager() الخاصة به للدمج مع ViewPager، إلا أنّه يتطلب مثيل TabLayoutMediator للتكامل مع ViewPager2.

يتعامل الكائن TabLayoutMediator أيضًا مع مهمة إنشاء عناوين الصفحات للكائن TabLayout، ما يعني أن فئة المحوِّل لا تحتاج إلى تجاوز getPageTitle():

Kotlin

// Integrating TabLayout with ViewPager
class CollectionDemoFragment : Fragment() {
    ...
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        val tabLayout = view.findViewById(R.id.tab_layout)
        tabLayout.setupWithViewPager(viewPager)
    }
    ...
}

class DemoCollectionPagerAdapter(fm: FragmentManager) : FragmentStatePagerAdapter(fm) {

    override fun getCount(): Int  = 4

    override fun getPageTitle(position: Int): CharSequence {
        return "OBJECT ${(position + 1)}"
    }
    ...
}

// Integrating TabLayout with ViewPager2
class CollectionDemoFragment : Fragment() {
    ...
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        val tabLayout = view.findViewById(R.id.tab_layout)
        TabLayoutMediator(tabLayout, viewPager) { tab, position ->
            tab.text = "OBJECT ${(position + 1)}"
        }.attach()
    }
    ...
}

Java

// Integrating TabLayout with ViewPager
public class CollectionDemoFragment extends Fragment {
    ...
    @Override
    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
        TabLayout tabLayout = view.findViewById(R.id.tab_layout);
        tabLayout.setupWithViewPager(viewPager);
    }
    ...
}

public class DemoCollectionPagerAdapter extends FragmentStatePagerAdapter {
    ...
    @Override
    public int getCount() {
        return 4;
    }

    @Override
    public CharSequence getPageTitle(int position) {
        return "OBJECT " + (position + 1);
    }
    ...
}

// Integrating TabLayout with ViewPager2
public class CollectionDemoFragment : Fragment() {
    ...
    @Override
    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
        TabLayout tabLayout = view.findViewById(R.id.tab_layout);
        new TabLayoutMediator(tabLayout, viewPager,
                (tab, position) -> tab.setText("OBJECT " + (position + 1))
        ).attach();
    }
    ...
}

دعم العناصر المضمنة القابلة للتمرير

لا تتيح السمة ViewPager2 في الأساس طرق عرض التمرير المدمَجة في الحالات التي يكون فيها عرض التمرير باتجاه كائن ViewPager2 الذي يحتوي عليه. على سبيل المثال، لن يعمل التمرير مع طريقة عرض التمرير العمودي داخل عنصر ViewPager2 باتجاه عمودي.

لإتاحة عرض التمرير داخل كائن ViewPager2 بالاتجاه نفسه، يجب استدعاء requestDisallowInterceptTouchEvent() على الكائن ViewPager2 عندما تتوقّع تمرير العنصر المضمّن بدلاً من ذلك. يوضح نموذج التمرير المدمج في ViewPager2 طريقة واحدة لحل هذه المشكلة باستخدام تنسيق برنامج تضمين مخصص متعدد الاستخدامات.

مراجع إضافية

لمعرفة المزيد من المعلومات عن ViewPager2، اطّلِع على المراجع الإضافية التالية.

عيّنات

الفيديوهات الطويلة