(いまさら)ViewPagerからViewPager2へ移行する
既存プロジェクトで継続使用していたViewPagerが非推奨になったのでViewPager2にいまさら置き換えた話
Created at

1760 Words
⚠️

依存パッケージのアップデート

androidx.fragment:fragment-ktx1.2.5から1.3.0に更新した結果、ActivityFragmentの扱い方に関する幾つかの変更が発生した。

今回の内容もそのひとつであり、「FragmentPagerAdapterが非推奨になったのでFragmentStateAdapterに移行してくれ(そして必然的にViewPagerやめてViewPager2に移行してくれ)」という問題に対応する。

他に対応したこと

startActivityForResult()を新APIに置き換える

ViewPager

ViewPagerはスワイプでページ切替できる複数のFragmentを表示するためのViewGroupであり、TabLayoutなどと一緒に使うことが多いように思う。

新しいプロジェクトでわざわざViewPagerを使用する利点はとくにないので、普通にViewPager2を導入すればいいのだが、既存のプロジェクトで横並びのシンプルなタブ実装にViewPagerを使用している場合に大急ぎで移行するほどの理由もとくに無い、ということで長らく放置していた。

ViewPagerでは表示内容の管理にFragmentPagerAdapterを使用しており、たとえば次のように用意する。

class HogeFragmentPagerAdapter(
    private val context : Contect,
    private val items : List<Item>,
    fragmentManager : FragmentManager
) : FragmentPagerAdapter(fragmentManager, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {
    override fun getItem(position: Int) : Fragment = items[position].createFragment()

    override fun getPageTitle(position: Int) : CharSequence? =
        context.getText(items[position].textId)

    override fun getCount() = items.size
}

// ------ //

// 表示内容例
enum class Item(
    /** タイトル文字列リソースID */
    @StringRes val textId: Int,

    /** 対応するフラグメントを生成 */
    val createFragment : ()->Fragment
) {
    FOO(R.string.foo, { FooFragment() }),

    BAR(R.string.bar, { BarFragment() }),

    BAZ(R.string.baz, { BazFragment() })
}
override fun onCreate(savedInstanceState: Bundle?) {
    // ...

    // `ViewPager`にアダプタを設定
    binding.viewPager.adapter = HogeFragmentPagerAdapter(
        this,
        Item.values(),
        supportFragmentManager
    )

    // `TabLayout`と同期
    binding.tabLayout.setupWithViewPager(binding.viewPager)
}

ViewPager を使用してフラグメント間をスライドする | Android デベロッパー | Android Developers

ViewPager2

ViewPager2ViewPagerの改良版であり、サポートが今後も継続するという他に幾つかの追加機能もある(縦方向に並べる、RTLサポート、DiffUtil利用など)。

ViewPager2 を使用してフラグメント間をスライドする | Android デベロッパー | Android Developers

ViewPager から ViewPager2 に移行する | Android デベロッパー | Android Developers

移行といっても大きく変わる部分は少なく、先のHogeFragmentPagerAdapterを差し替える場合は次のように書き換える。

class HogeFragmentStateAdapter(
    activity : FragmentActivity,
    private val items : List<Item>
) : FragmentStateAdapter(acitivity) {

    override fun getItemCount() : Int = items.size

    override fun createFragment(position: Int) : Fragment = items[position].createFragment()
}
override fun onCreate(savedInstanceState: Bundle?) {
    // ...

    // `ViewPager2`にアダプタを設定
    binding.viewPager2.adapter = HogeFragmentStateAdapter(this, Item.values())

    // `TabLayout`と同期
    TabLayoutMediator(binding.tabLayout, binding.viewPager2) { tab, position ->
        tab.setText(Item.values()[position].textId)
    }.attach()
}

コンストラクタ

FragmentStateAdapterのコンストラクタにはFragmentActivityFragmentFragmentManager, Lifecycleのどれかを渡す。

アダプタがフラグメントを扱うのに使用するFragmentManagerは、FragmentActivityを渡す場合FragmentActivity#supportFragmentManagerが、Fragmentを渡す場合はFragment#childFragmentManagerが使用される。

TabLayoutと同期

TabLayoutMediatorを使用して、ページとタブ表示の同期を行う。

最後にattach()を忘れると外観に反映されないので注意が必要。

生成済みのページのFragmentを取得する

「現在表示しているFragment」など必要になる場合、アダプタのコンストラクタに与えたFragmentManagerから次のようにして対応インデックスのFragmentを取得できる。

class HogeFragmentStateAdapter(
    private val activity : FragmentActivity
) : FragmentStateAdapter(activity) {
    // ...

    fun findFragment(poisition: Int) : Fragment? =
        activity.supportFragmentManager.findFragmentByTag("f$position")
}

アダプタが管理するFragment"f" + インデックスのタグがついてFragmentManagerに登録されている。

itemCount

FragmentPagerAdapterではgetCount()メソッドを使用していたため、プロパティアクセス時にはcountを使用したが、
FragmentStateAdapterではgetItemCount()に変更しているため、プロパティアクセスでアイテム数を取得している箇所がある場合はadapter.countの部分をadapter.itemCountに修正する必要がある。

See Also