ย้ายข้อมูลจาก ViewPager ไปยัง ViewPager2

ViewPager2 เป็นเวอร์ชันที่ปรับปรุงแล้วของไลบรารี ViewPager ซึ่งมีฟังก์ชันการทำงานที่ดียิ่งขึ้นและช่วยแก้ปัญหาที่พบได้ทั่วไปเมื่อใช้ ViewPager หากแอปของคุณใช้ ViewPager อยู่แล้ว โปรดอ่านหน้านี้เพื่อดูข้อมูลเพิ่มเติมเกี่ยวกับการย้ายข้อมูลไปยัง ViewPager2

หากต้องการใช้ ViewPager2 ในแอปและปัจจุบันไม่ได้ใช้ ViewPager โปรดอ่านเลื่อนระหว่าง Fragment โดยใช้ 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) ระบบจะเปิดใช้การแบ่งหน้าแบบ RTL โดยอัตโนมัติเมื่อเหมาะสมตามภาษา แต่คุณก็เปิดใช้การแบ่งหน้าแบบ 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()

คอลเล็กชัน Fragment ที่แก้ไขได้

ViewPager2 รองรับการแบ่งหน้าผ่านคอลเล็กชัน Fragment ที่แก้ไขได้ โดยเรียกใช้ notifyDatasetChanged() เพื่ออัปเดต UI เมื่อคอลเล็กชันที่เกี่ยวข้องมีการเปลี่ยนแปลง

ซึ่งหมายความว่าแอปของคุณสามารถแก้ไขคอลเล็กชัน Fragment แบบไดนามิกที่รันไทม์ได้ และ 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 ใช้คลาส Abstract 3 คลาสที่แตกต่างกัน โดยขึ้นอยู่กับ Use Case ViewPager2 ใช้คลาสนามธรรมเพียง 2 คลาสเท่านั้น

สำหรับออบเจ็กต์ ViewPager แต่ละรายการที่คุณกำลังแปลงเป็นออบเจ็กต์ ViewPager2 ให้อัปเดตคลาสอะแดปเตอร์เพื่อขยายคลาส Abstract ที่เหมาะสมดังนี้

  • เมื่อ ViewPager ใช้ PagerAdapter เพื่อเลื่อนดูมุมมอง ให้ใช้ RecyclerView.Adapter กับ ViewPager2
  • เมื่อ ViewPager ใช้ FragmentPagerAdapter เพื่อแบ่งหน้าผ่าน Fragment จำนวนน้อยและคงที่ ให้ใช้ FragmentStateAdapter กับ ViewPager2
  • เมื่อ ViewPager ใช้ FragmentStatePagerAdapter เพื่อแบ่งหน้าผ่านส่วนย่อยจำนวนมากหรือไม่ทราบจำนวน ให้ใช้ FragmentStateAdapter กับ ViewPager2

พารามิเตอร์ Constructor

คลาสอะแดปเตอร์ที่อิงตาม Fragment ซึ่งรับค่ามาจาก FragmentPagerAdapter หรือ FragmentStatePagerAdapter จะยอมรับออบเจ็กต์ FragmentManager รายการเดียวเป็นพารามิเตอร์เครื่องมือสร้างเสมอ เมื่อขยาย FragmentStateAdapter สำหรับคลาสอะแดปเตอร์ ViewPager2 คุณจะมีตัวเลือกต่อไปนี้สำหรับพารามิเตอร์คอนสตรัคเตอร์แทน

  • ออบเจ็กต์ FragmentActivity หรือออบเจ็กต์ Fragment ที่มีออบเจ็กต์ ViewPager2 อยู่ ในกรณีส่วนใหญ่ วิธีนี้เป็นตัวเลือกที่ดีกว่า
  • ออบเจ็กต์ FragmentManager และออบเจ็กต์ Lifecycle

คลาส Adapter ที่อิงตามมุมมองซึ่งรับค่าโดยตรงจาก RecyclerView.Adapter ไม่จำเป็นต้องมีพารามิเตอร์เครื่องมือสร้าง

วิธีการลบล้าง

คลาสอะแดปเตอร์ยังต้องลบล้างเมธอดต่างๆ สำหรับ ViewPager2 ซึ่งแตกต่างจากที่ทำสำหรับ ViewPager ดังนี้

  • ลบล้าง getItemCount() แทน getCount() วิธีการนี้ไม่มีการเปลี่ยนแปลงใดๆ นอกเหนือจากชื่อ
  • แทนที่จะใช้ getItem() ให้ลบล้าง createFragment() ในคลาสอะแดปเตอร์ที่อิงตาม Fragment ตรวจสอบว่าเมธอด createFragment() ใหม่จะระบุอินสแตนซ์ Fragment ใหม่ทุกครั้งที่มีการเรียกใช้ฟังก์ชันแทนการใช้อินสแตนซ์ซ้ำ

สรุป

สรุปคือ หากต้องการแปลงคลาสอะแดปเตอร์ ViewPager เพื่อใช้กับ ViewPager2 คุณต้องทำการเปลี่ยนแปลงต่อไปนี้

  1. เปลี่ยนคลาสใหญ่สุดเป็น RecyclerView.Adapter สำหรับการแบ่งหน้าผ่านมุมมอง หรือ FragmentStateAdapter สำหรับการแบ่งหน้าผ่าน Fragment
  2. เปลี่ยนพารามิเตอร์คอนสตรัคเตอร์ในคลาสอะแดปเตอร์ที่อิงตาม Fragment
  3. ลบล้าง getItemCount() แทน getCount()
  4. ลบล้าง createFragment() แทน getItem() ในคลาสอะแดปเตอร์ที่อิงตาม Fragment

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 ซึ่งหมายความว่าหากต้องการใช้ คุณต้องเพิ่มทรัพยากร Dependency ที่เหมาะสมลงในไฟล์ 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 แสดงวิธีหนึ่งในการแก้ปัญหานี้ด้วยเลย์เอาต์ Wrapper ที่กำหนดเองที่อเนกประสงค์

แหล่งข้อมูลเพิ่มเติม

ดูข้อมูลเพิ่มเติมเกี่ยวกับ ViewPager2 ได้ที่แหล่งข้อมูลเพิ่มเติมต่อไปนี้

วิดีโอ