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 ページングは、ロケールに応じて必要に応じて自動的に有効になりますが、ViewPager2
要素の RTL ページングは、次のように android:layoutDirection
属性を設定することで手動で有効にすることもできます。
<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
を使用してビューをページ スルーした場合、RecyclerView.Adapter
をViewPager2
とともに使用します。ViewPager
がFragmentPagerAdapter
を使用して、固定数の小さなフラグメントをページングした場合は、ViewPager2
でFragmentStateAdapter
を使用します。ViewPager
がFragmentStatePagerAdapter
を使用して多数のフラグメントまたは未知のフラグメントをページングした場合は、FragmentStateAdapter
をViewPager2
とともに使用します。
コンストラクタ パラメータ
FragmentPagerAdapter
または FragmentStatePagerAdapter
を継承するフラグメント ベースのアダプター クラスは、常に単一の FragmentManager
オブジェクトをコンストラクタ パラメータとして受け入れます。ViewPager2
アダプター クラスの FragmentStateAdapter
を拡張する場合、コンストラクタ パラメータに以下のオプションがあります。
ViewPager2
オブジェクトが存在するFragmentActivity
オブジェクトまたはFragment
オブジェクト。ほとんどの場合、この方法をおすすめします。FragmentManager
オブジェクトとLifecycle
オブジェクト。
RecyclerView.Adapter
から直接継承するビューベースのアダプター クラスには、コンストラクタ パラメータは必要ありません。
メソッドをオーバーライド
アダプタ クラスでは、ViewPager
とは異なる ViewPager2
のメソッドをオーバーライドする必要もあります。
getCount()
ではなく、getItemCount()
をオーバーライドします。名前以外、このメソッドは変更されていません。- フラグメント ベースのアダプタクラスでは、
getItem()
ではなく、createFragment()
をオーバーライドします。新しいcreateFragment()
メソッドで、インスタンスを再利用するのではなく、関数が呼び出されるたびに常に新しいフラグメント インスタンスを生成するようにします。
まとめ
要約すると、ViewPager
アダプター クラスを ViewPager2
で使用できるように変換するには、次の変更を行う必要があります。
- ビューのページングの場合はスーパークラスを
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 のネストされたスクロールのサンプルは、汎用性の高いカスタム ラッパー レイアウトを使用してこの問題を解決する 1 つの方法を示しています。
参考情報
ViewPager2
の詳細については、以下の参考リンクをご覧ください。
サンプル
- ViewPager2 サンプル(GitHub)
動画
- ページをめくる: ViewPager2 への移行(Android Dev Summit 2019)