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() メソッドを使用してプログラムで設定することもできます。
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 を継承するフラグメント ベースのアダプター クラスは、常に 1 つの FragmentManager オブジェクトをコンストラクタ パラメータとして受け取ります。ViewPager2 アダプター クラスの FragmentStateAdapter を拡張する場合は、コンストラクタ パラメータに次のオプションを使用できます。
ViewPager2オブジェクトが存在するFragmentActivityオブジェクトまたはFragmentオブジェクト。ほとんどの場合、この方法をおすすめします。FragmentManagerオブジェクトとLifecycleオブジェクト。
RecyclerView.Adapter から直接継承するビューベースのアダプター クラスには、コンストラクタ パラメータは必要ありません。
メソッドをオーバーライド
また、アダプター クラスは、ViewPager の場合とは異なる ViewPager2 のメソッドをオーバーライドする必要があります。
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 インテグレーションに変更が加えられています。現在、TabLayout オブジェクトを含む ViewPager を使用してナビゲーション用の水平タブを表示している場合は、ViewPager2 と統合するために TabLayout オブジェクトをリファクタリングする必要があります。
TabLayout が ViewPager2 から切り離され、マテリアル コンポーネントの一部として利用できるようになりました。つまり、これを使用するには、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 と統合しますが、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 への移行(Android Dev Summit 2019)