從 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() 在基礎集合變更時更新使用者介面。

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

差異比較工具

ViewPager2RecyclerView 為基礎建構而成,也就是說,它可以存取 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 物件,更新轉接器類別以擴充適當的抽象類別,如下所示:

建構函式參數

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

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

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

覆寫方法

您的轉接程式類別也需要覆寫 ViewPager2ViewPager 的不同方法:

  • 覆寫 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,則會在相同層級的 ViewPager2 元素正上方宣告 TabLayout 元素:

<!-- 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,請參閱下列其他資源:

範例

影片