依存パッケージのアップデート
androidx.fragment:fragment-ktx
を1.2.5
から1.3.0
に更新した結果、Activity
やFragment
の扱い方に関する幾つかの変更が発生した。
今回の内容もそのひとつであり、「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
ViewPager2
はViewPager
の改良版であり、サポートが今後も継続するという他に幾つかの追加機能もある(縦方向に並べる、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
のコンストラクタにはFragmentActivity
、Fragment
、FragmentManager, 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
に修正する必要がある。