ViewPager から ViewPager2 に移行する

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

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

FragmentPagerAdapter または FragmentStatePagerAdapter を継承するフラグメント ベースのアダプター クラスは、常に 1 つの FragmentManager オブジェクトをコンストラクタ パラメータとして受け取ります。ViewPager2 アダプター クラスの FragmentStateAdapter を拡張する場合は、コンストラクタ パラメータに次のオプションを使用できます。

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

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

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

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

  • 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 インテグレーションに変更が加えられています。現在、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 の詳細については、以下の参考リンクをご覧ください。

動画