ViewPager から ViewPager2 に移行する

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

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 は 3 つの抽象クラスを使用していました。ViewPager2 の場合、使用する抽象クラスは 2 つだけで済みます。

ViewPager2 オブジェクトに変換する ViewPager オブジェクトごとに、以下のように、アダプター クラスを更新し、該当する抽象クラスを拡張してください。

コンストラクタ パラメータ

FragmentPagerAdapterFragmentStatePagerAdapter から継承するフラグメント ベースのアダプター クラスの場合、常に単一の FragmentManager オブジェクトをコンストラクタ パラメータとして受け入れます。ViewPager2 アダプター クラス向けに FragmentStateAdapter を拡張する場合は、コンストラクタ パラメータとして、代わりに以下の選択肢があります。

  • ViewPager2 オブジェクトが存在する FragmentActivity オブジェクトまたは Fragment オブジェクト。ほとんどの場合、この方法をおすすめします。
  • FragmentManager オブジェクトと Lifecycle オブジェクト。

RecyclerView.Adapter から直接継承するビューベースのアダプター クラスの場合、コンストラクタ パラメータは必要ありません。

メソッドをオーバーライドする

また、アダプター クラスがオーバーライドする必要のあるメソッドに関しても、ViewPager2 の場合と ViewPager の場合では異なります。

  • getCount() ではなく、getItemCount() をオーバーライドします。このメソッドは、名前以外に変更点はありません。
  • フラグメント ベース アダプター クラスの場合、getItem() ではなく、createFragment() をオーバーライドします。新しい createFragment() メソッドは、インスタンスを再利用するのではなく、関数が呼び出されるたびに常に新しいフラグメント インスタンスを提供します。

まとめ

まとめると、ViewPager2 で使用できるように ViewPager アダプター クラスを変換する場合、以下のように変更する必要があります。

  1. ビューのページングの場合、スーパークラスを RecyclerView.Adapter に変更します。フラグメントのページングの場合は FragmentStateAdapter に変更します。
  2. フラグメント ベース アダプター クラスのコンストラクタ パラメータを変更します。
  3. getCount() ではなく getItemCount() をオーバーライドします。
  4. フラグメント ベース アダプター クラスの場合、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 インテグレーションに変更が加えられています。現在 ViewPagerTabLayout オブジェクトを使用してナビゲーション用の水平方向タブを表示している場合、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 の詳細については、以下の参考リンクをご覧ください。

サンプル

動画