ViewPager2
は、ViewPager
ライブラリの機能を拡張した改良版であり、ViewPager
を使用していたときによく見られた問題点が解決されています。既存のアプリで ViewPager
を使用している場合は、このページで ViewPager2
に移行する手順についてご確認ください。
現在 ViewPager
を使用していないアプリで ViewPager2
を使用する予定の場合は、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() メソッドを使用してプログラマティックに設定することもできます。
RTL(右から左)のサポート
ViewPager2
は、RTL(右から左)方向ページングをサポートしています。RTL 方向ページングは、該当する言語 / 地域の場合に自動的に有効になります。また、android:layoutDirection
属性を設定することで、ViewPager2
要素の 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
は 3 つの抽象クラスを使用していました。ViewPager2
の場合、使用する抽象クラスは 2 つだけで済みます。
ViewPager2
オブジェクトに変換する ViewPager
オブジェクトごとに、以下のように、アダプター クラスを更新し、該当する抽象クラスを拡張してください。
ViewPager
でPagerAdapter
を使用してビューをページングしていた場合、ViewPager2
ではRecyclerView.Adapter
を使用します。ViewPager
でFragmentPagerAdapter
を使用して少数かつ固定数のフラグメントをページングしていた場合、ViewPager2
ではFragmentStateAdapter
を使用します。ViewPager
でFragmentStatePagerAdapter
を使用して大量または不明数のフラグメントをページングした場合、ViewPager2
ではFragmentStateAdapter
を使用します。
コンストラクタ パラメータ
FragmentPagerAdapter
や FragmentStatePagerAdapter
から継承するフラグメント ベースのアダプター クラスの場合、常に単一の FragmentManager
オブジェクトをコンストラクタ パラメータとして受け入れます。ViewPager2
アダプター クラス向けに FragmentStateAdapter
を拡張する場合は、コンストラクタ パラメータとして、代わりに以下の選択肢があります。
ViewPager2
オブジェクトが存在するFragmentActivity
オブジェクトまたはFragment
オブジェクト。ほとんどの場合、この方法をおすすめします。FragmentManager
オブジェクトとLifecycle
オブジェクト。
RecyclerView.Adapter
から直接継承するビューベースのアダプター クラスの場合、コンストラクタ パラメータは必要ありません。
メソッドをオーバーライドする
また、アダプター クラスがオーバーライドする必要のあるメソッドに関しても、ViewPager2
の場合と ViewPager
の場合では異なります。
getCount()
ではなく、getItemCount()
をオーバーライドします。このメソッドは、名前以外に変更点はありません。- フラグメント ベース アダプター クラスの場合、
getItem()
ではなく、createFragment()
をオーバーライドします。新しいcreateFragment()
メソッドは、インスタンスを再利用するのではなく、関数が呼び出されるたびに常に新しいフラグメント インスタンスを提供します。
まとめ
まとめると、ViewPager2
で使用できるように ViewPager
アダプター クラスを変換する場合、以下のように変更する必要があります。
- ビューのページングの場合、スーパークラスを
RecyclerView.Adapter
に変更します。フラグメントのページングの場合はFragmentStateAdapter
に変更します。 - フラグメント ベース アダプター クラスのコンストラクタ パラメータを変更します。
getCount()
ではなくgetItemCount()
をオーバーライドします。- フラグメント ベース アダプター クラスの場合、
getItem()
ではなく、createFragment()
をオーバーライドします。
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
オブジェクトを使用してナビゲーション用の水平方向タブを表示している場合、ViewPager2
とインテグレーションするには、TabLayout
オブジェクトをリファクタリングする必要があります。
TabLayout
は、ViewPager2
からは切り離され、現在は Material Components の一部として利用できます。そのため、使用するには、適切な依存関係を build.gradle
ファイルに追加する必要があります。
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
は、ViewPager
とインテグレーションする場合は独自の setupWithViewPager()
メソッドを使用しますが、ViewPager2
とインテグレーションする場合は TabLayoutMediator
インスタンスが必要になります。
また、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
の詳細については、以下の参考リンクをご覧ください。
サンプル
- ViewPager2 サンプル(GitHub)
動画
- ページをめくる: ViewPager2 への移行(Android Dev Summit 2019)