從 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) 分頁。系統會根據語言代碼自動啟用 RTL 分頁功能 (如適用),但您也可以設定 ViewPager2 元素的 android:layoutDirection 屬性,手動啟用 RTL 分頁功能:

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

您也可以使用 setLayoutDirection() 方法,以程式輔助方式設定這項屬性。

可修改的片段集合

ViewPager2 支援透過可修改的片段集合進行分頁,並在基礎集合變更時呼叫 notifyDatasetChanged() 更新 UI。

這表示應用程式可以在執行階段動態修改片段集合,而 ViewPager2 會正確顯示修改後的集合。

DiffUtil

ViewPager2 是以 RecyclerView 為基礎建構而成,因此可以存取 DiffUtil 公用程式類別。這會帶來多項好處,但最值得注意的是,ViewPager2 物件原生利用 RecyclerView 類別的資料集變更動畫。

將應用程式遷移至 ViewPager2

請按照下列步驟,將應用程式中的 ViewPager 物件更新為 ViewPager2:

更新 XML 版面配置檔案

首先,請將 XML 版面配置檔案中的 ViewPager 元素替換為 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 只使用兩個抽象類別。

針對要轉換為 ViewPager2 物件的每個 ViewPager 物件,請更新轉接器類別,以擴充適當的抽象類別,如下所示:

建構函式參數

繼承自 FragmentPagerAdapter 或 FragmentStatePagerAdapter 的片段式轉接器類別一律接受單一 FragmentManager 物件做為建構函式參數。為 ViewPager2 轉接程式類別擴充 FragmentStateAdapter 時,您可以改用下列建構函式參數選項:

  • ViewPager2 物件所在的 FragmentActivity 物件或 Fragment 物件。在大多數情況下,這是較好的選擇。
  • FragmentManager 物件和 Lifecycle 物件。

直接沿用 RecyclerView.Adapter 的檢視畫面型轉接器類別不需要建構函式參數。

覆寫方法

轉接程式類別也需要覆寫 ViewPager2 的不同方法,而非 ViewPager 的方法:

  • 覆寫 getItemCount(),而非 getCount()。除了名稱之外,這個方法沒有其他變更。
  • 在以片段為基礎的轉接器類別中,覆寫 createFragment(),而非 getItem()。請確保新的 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 檔案中加入適當的依附元件:

Groovy

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

Kotlin

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

您也需要變更 XML 版面配置檔案階層中的 TabLayout 元素位置。使用 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 物件中支援捲動檢視畫面,您必須在預期捲動巢狀元素時,對 ViewPager2 物件呼叫 requestDisallowInterceptTouchEvent()。ViewPager2 巢狀捲動範例示範如何使用多功能自訂包裝函式版面配置解決這個問題。

其他資源

如要進一步瞭解 ViewPager2,請參閱下列其他資源:

影片