ViewPager から ViewPager2 に移行する

ViewPager2ViewPager ライブラリの改良版であり、機能が強化され、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

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 オブジェクトごとに、アダプター クラスを更新して、次のように適切な抽象クラスを拡張します。

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

FragmentPagerAdapter または FragmentStatePagerAdapter を継承するフラグメント ベースのアダプター クラスは、常に単一の FragmentManager オブジェクトをコンストラクタ パラメータとして受け入れます。ViewPager2 アダプター クラスの FragmentStateAdapter を拡張する場合、コンストラクタ パラメータに以下のオプションがあります。

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

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

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

アダプタ クラスでは、ViewPager とは異なる ViewPager2 のメソッドをオーバーライドする必要もあります。

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

まとめ

要約すると、ViewPager アダプター クラスを ViewPager2 で使用できるように変換するには、次の変更を行う必要があります。

  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 インテグレーションに変更が加えられています。現在、TabLayout オブジェクトで ViewPager を使用してナビゲーション用の水平タブを表示している場合は、ViewPager2 と統合するために TabLayout オブジェクトをリファクタリングする必要があります。

TabLayoutViewPager2 から切り離され、マテリアル コンポーネントの一部として利用できるようになりました。つまり、これを使用するには、適切な依存関係を 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 の詳細については、以下の参考リンクをご覧ください。

サンプル

動画