מעבר מ-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, מעדכנים את מחלקת המתאם כדי להרחיב את המחלקה המופשטת המתאימה, באופן הבא:

פרמטרים של בנאי (constructor)

מחלקות של מתאמים שמבוססות על מקטעים ויורשות מ-FragmentPagerAdapter או מ-FragmentStatePagerAdapter תמיד מקבלות אובייקט FragmentManager יחיד כפרמטר של פונקציית ה-constructor. כשמרחיבים את FragmentStateAdapter עבור מחלקת מתאם של ViewPager2, יש את האפשרויות הבאות לפרמטרים של בנאי:

  • האובייקט FragmentActivity או האובייקט Fragment שבהם נמצא האובייקט ViewPager2. ברוב המקרים, זו האפשרות הטובה יותר.
  • אובייקט FragmentManager ואובייקט Lifecycle.

במחלקות של מתאמים מבוססי-תצוגה שיורשות ישירות מ-RecyclerView.Adapter, אין צורך בפרמטר של פונקציית בנאי.

שיטות לשינוי ברירת המחדל

בנוסף, מחלקות המתאם צריכות לשנות שיטות שונות ב-ViewPager2 בהשוואה ל-ViewPager:

  • במקום getCount(), משנים את getItemCount(). חוץ מהשם, השיטה הזו לא השתנתה.
  • במקום getItem(), צריך לשנות את createFragment() במחלקות של מתאמים שמבוססים על מקטעים. חשוב לוודא שה-method החדשה של createFragment() תמיד מספקת מופע חדש של מקטע בכל פעם שהפונקציה נקראת, במקום לעשות שימוש חוזר במופעים.

סיכום

לסיכום, כדי להמיר מחלקת מתאם של ViewPager לשימוש עם ViewPager2, צריך לבצע את השינויים הבאים:

  1. משנים את המחלקה הראשית ל-RecyclerView.Adapter כדי לדפדף בתצוגות, או ל-FragmentStateAdapter כדי לדפדף בפרגמנטים.
  2. משנים את הפרמטרים של ה-constructor במחלקות של מתאמים שמבוססים על מקטעים.
  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 משתמש ב-method‏ 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 בתצוגה מקוננת מוצגת דרך אחת לפתרון הבעיה באמצעות פריסת wrapper מותאמת אישית ורסטילית.

מקורות מידע נוספים

כדי לקבל מידע נוסף על ViewPager2, אפשר לעיין במקורות המידע הבאים.

סרטונים