Di chuyển từ ViewPager sang ViewPager2

ViewPager2 là phiên bản cải tiến của thư viện ViewPager cung cấp chức năng nâng cao và giải quyết những khó khăn thường gặp khi sử dụng ViewPager. Nếu ứng dụng của bạn đã sử dụng ViewPager, hãy đọc trang này để tìm hiểu thêm về cách di chuyển sang ViewPager2.

Nếu bạn muốn sử dụng ViewPager2 trong ứng dụng và hiện không sử dụng ViewPager, hãy đọc nội dung Trượt giữa các mảnh bằng ViewPager2Tạo thành phần hiển thị vuốt có thẻ bằng ViewPager2 để biết thêm thông tin.

Lợi ích của việc di chuyển sang ViewPager2

Lý do chính để di chuyển là ViewPager2 đang được hỗ trợ phát triển còn ViewPager thì không. Tuy nhiên, ViewPager2 cũng cung cấp một số ưu điểm cụ thể khác.

Hỗ trợ hướng dọc

ViewPager2 hỗ trợ chế độ phân trang theo chiều dọc ngoài chế độ phân trang theo chiều ngang truyền thống. Bạn có thể bật tính năng phân trang theo chiều dọc cho một phần tử ViewPager2 bằng cách đặt thuộc tính android:orientation của phần tử đó:

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

Bạn cũng có thể đặt thuộc tính này theo phương thức lập trình bằng phương thức setOrientation().

Hỗ trợ từ phải sang trái

ViewPager2 hỗ trợ phân trang từ phải sang trái (RTL). Tính năng phân trang RTL được tự động bật khi thích hợp dựa trên ngôn ngữ, nhưng bạn cũng có thể bật tính năng phân trang RTL theo cách thủ công cho phần tử ViewPager2 bằng cách đặt thuộc tính android:layoutDirection của phần tử đó:

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

Bạn cũng có thể đặt thuộc tính này theo phương thức lập trình bằng cách sử dụng phương thức setLayoutDirection().

Tập hợp mảnh có thể sửa đổi

ViewPager2 hỗ trợ tính năng phân trang thông qua một tập hợp các mảnh có thể sửa đổi, gọi notifyDatasetChanged() để cập nhật giao diện người dùng khi bộ sưu tập cơ bản thay đổi.

Tức là ứng dụng của bạn có thể tự động sửa đổi bộ sưu tập mảnh trong thời gian chạy và ViewPager2 sẽ hiển thị chính xác bộ sưu tập đã sửa đổi.

DiffUtil

ViewPager2 được xây dựng trên RecyclerView, tức là có quyền truy cập vào lớp tiện ích DiffUtil. Điều này mang lại một số lợi ích, nhưng đáng chú ý nhất là các đối tượng ViewPager2 vốn có thể tận dụng ảnh động thay đổi tập dữ liệu của lớp RecyclerView.

Di chuyển ứng dụng của bạn sang ViewPager2

Hãy làm theo các bước sau để cập nhật đối tượng ViewPager trong ứng dụng của bạn thành ViewPager2:

Cập nhật tệp bố cục XML

Trước tiên, hãy thay thế các phần tử ViewPager trong tệp bố cục XML bằng phần tử 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" />

Cập nhật các lớp bộ chuyển đổi

Khi sử dụng ViewPager, bạn phải mở rộng lớp bộ chuyển đổi đã cung cấp trang mới cho đối tượng. Tuỳ thuộc vào trường hợp sử dụng, ViewPager sử dụng ba lớp trừu tượng khác nhau. ViewPager2 chỉ sử dụng hai lớp trừu tượng.

Đối với mỗi đối tượng ViewPager mà bạn đang chuyển đổi thành đối tượng ViewPager2, hãy cập nhật lớp bộ chuyển đổi để mở rộng lớp trừu tượng thích hợp như sau:

Tham số hàm khởi tạo

Các lớp chuyển đổi dựa trên mảnh kế thừa từ FragmentPagerAdapter hoặc FragmentStatePagerAdapter luôn chấp nhận một đối tượng FragmentManager duy nhất làm tham số hàm khởi tạo. Khi mở rộng FragmentStateAdapter cho một lớp bộ chuyển đổi ViewPager2, bạn sẽ có các lựa chọn sau cho tham số hàm khởi tạo:

  • Đối tượng FragmentActivity hoặc đối tượng Fragment là nơi chứa đối tượng ViewPager2. Trong hầu hết các trường hợp, đây là lựa chọn phù hợp hơn.
  • Đối tượng FragmentManager và đối tượng Lifecycle.

Các lớp chuyển đổi dựa trên khung hiển thị kế thừa trực tiếp từ RecyclerView.Adapter không yêu cầu tham số hàm khởi tạo.

Phương thức ghi đè

Các lớp bộ chuyển đổi của bạn cũng cần ghi đè các phương thức khác nhau cho ViewPager2 so với ViewPager:

  • Thay vì getCount(), hãy ghi đè getItemCount(). Ngoài tên, phương thức này không thay đổi.
  • Thay vì getItem(), hãy ghi đè createFragment() trong các lớp bộ chuyển đổi dựa trên mảnh. Hãy đảm bảo phương thức createFragment() mới của bạn luôn cung cấp một thực thể mới của mảnh mỗi khi hàm được gọi thay vì sử dụng lại các thực thể.

Tóm tắt

Tóm lại, để chuyển đổi một lớp bộ chuyển đổi ViewPager để sử dụng với ViewPager2, bạn phải thực hiện những thay đổi sau:

  1. Thay đổi lớp cấp cao thành RecyclerView.Adapter để phân trang thông qua các thành phần hiển thị hoặc FragmentStateAdapter để phân trang thông qua các mảnh.
  2. Thay đổi các tham số hàm khởi tạo trong các lớp bộ chuyển đổi dựa trên mảnh.
  3. Ghi đè getItemCount() thay vì getCount().
  4. Ghi đè createFragment() thay vì getItem() trong các lớp bộ chuyển đổi dựa trên mảnh.

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;
    }
}

Tái cấu trúc giao diện TabLayout

ViewPager2 giới thiệu các thay đổi đối với tính năng tích hợp TabLayout. Nếu đang sử dụng ViewPager với đối tượng TabLayout nhằm hiển thị các thẻ theo chiều ngang cho việc điều hướng, bạn cần tái cấu trúc đối tượng TabLayout để tích hợp với ViewPager2.

TabLayout đã được tách khỏi ViewPager2 và hiện có sẵn như một phần của thành phần Material. Điều này có nghĩa là để sử dụng phần tử này, bạn cần thêm phần phụ thuộc thích hợp vào tệp build.gradle:

Groovy

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

Kotlin

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

Bạn cũng cần thay đổi vị trí của phần tử TabLayout trong hệ phân cấp của tệp bố cục XML. Với ViewPager, phần tử TabLayout được khai báo là phần tử con của phần tử ViewPager; nhưng với ViewPager2, phần tử TabLayout được khai báo ngay phía trên phần tử ViewPager2, ở cùng cấp:

<!-- 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>

Cuối cùng, bạn phải cập nhật mã đính kèm đối tượng TabLayout vào đối tượng ViewPager. Mặc dù TabLayout dùng phương thức setupWithViewPager() riêng để tích hợp với ViewPager, nhưng bạn phải có một thực thể TabLayoutMediator để tích hợp với ViewPager2.

Đối tượng TabLayoutMediator cũng xử lý tác vụ tạo tiêu đề trang cho đối tượng TabLayout, nghĩa là lớp bộ chuyển đổi không cần ghi đè 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();
    }
    ...
}

Hỗ trợ các phần tử lồng nhau có thể cuộn

ViewPager2 không hỗ trợ sẵn các thành phần hiển thị cuộn lồng nhau trong trường hợp thành phần hiển thị cuộn có cùng hướng với đối tượng ViewPager2 chứa thành phần hiển thị đó. Ví dụ: thao tác cuộn sẽ không hoạt động đối với khung hiển thị cuộn dọc bên trong đối tượng ViewPager2 theo hướng dọc.

Để hỗ trợ khung hiển thị cuộn bên trong đối tượng ViewPager2 có cùng hướng, bạn phải gọi requestDisallowInterceptTouchEvent() trên đối tượng ViewPager2 khi muốn cuộn phần tử lồng nhau. Mẫu cuộn lồng ViewPager2 minh hoạ một cách giải quyết vấn đề này bằng bố cục trình bao bọc tuỳ chỉnh linh hoạt.

Tài nguyên khác

Để tìm hiểu thêm về ViewPager2, hãy xem các tài nguyên bổ sung sau đây.

Mẫu

Video