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 คุณต้องทำการเปลี่ยนแปลงต่อไปนี้
- เปลี่ยนคลาสใหญ่สุดเป็น
RecyclerView.Adapterสำหรับการแบ่งหน้าผ่านมุมมอง หรือFragmentStateAdapterสำหรับการแบ่งหน้าผ่าน Fragment - เปลี่ยนพารามิเตอร์คอนสตรัคเตอร์ในคลาสอะแดปเตอร์ที่อิงตาม Fragment
- ลบล้าง
getItemCount()แทนgetCount() - ลบล้าง
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 ได้ที่แหล่งข้อมูลเพิ่มเติมต่อไปนี้
วิดีโอ
- Turning the Page: Migrating to ViewPager2 (Android Dev Summit '19)