نقل البيانات من 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 التنقّل بين الصفحات من اليمين إلى اليسار (RTL). يتم تفعيل التنقّل بين الصفحات من اليمين إلى اليسار تلقائيًا عند الاقتضاء استنادًا إلى اللغة، ولكن يمكنك أيضًا تفعيل التنقّل بين الصفحات من اليمين إلى اليسار يدويًا لعنصر 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 المجموعة المعدَّلة بشكل صحيح.

DiffUtil

يستند 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

يقدّم الإصدار ViewPager2 تغييرات على عملية دمج TabLayout. إذا كنت تستخدم حاليًا 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"، يُرجى الاطّلاع على المراجع الإضافية التالية.

الفيديوهات