LoginSignup
3
1

More than 1 year has passed since last update.

【Android】ViewPager2+FragmentStateAdapterでFragmentを置き換える

Posted at

ViewPager2 + FragmentStateAdapterでFragmentを追加・削除しようとしてハマった時のまとめ。

やりたいこと

  • ViewPagerにセットしたFragmentを別のFragmentに置換したい

方針

Android Developersに以下のような記載がありました。

ViewPager2 は、編集可能なフラグメント コレクションのページングをサポートしています。基盤コレクションが変更されたときに、notifyDatasetChanged() を呼び出して UI を更新します。
これにより、アプリは、実行時にフラグメント コレクションを動的に編集できるようになり、編集されたコレクションを ViewPager2 が正確に表示します。

そのため方針としては以下のように実装していきます。

  • FragmentStateAdapterで表示するFragmentのリストを保持して、表示するFragmnetを操作する(add/remove)
  • Fragmentのリストを操作したら、notify系のメソッドでUIを更新する

試したこと

FragmentStateAdapterを実装してみる

今回は以下の2パターンの置き換えを実装します。

  • FragmentOne -> FragmentA
  • FragmentTwo -> FragmentA, FragmentB

置き換える方法の判別はEnum classを利用して行います。

ReplacePattern.kt
enum class ReplacePattern {
    OneToA,
    TwoToAB
}
ViewPagerAdapter.kt
class ViewPagerAdapter (fragment: Fragment): FragmentStateAdapter(fragment) {
    private val fragmentList = mutableListOf<Fragment>()

    /**
     * 中略
     **/

    fun replaceFragment(pattern: ReplacePattern){
        //FragmentOneをremoveして、FragmentAに置き換える
        if (pattern == ReplacePattern.A) {
            fragmentList.removeFirst()
            fragmentList.add(0, (FragmentA()))
        }

        //FragmentTwoをremoveして、FragmentA, Bに置き換える
        if (pattern == ReplacePattern.AB){
            fragmentList.removeAt(1)
            fragmentList.add(1, (FragmentA()))
            fragmentList.add(2, (FragmentB()))
        }

        //notifyを呼んでUIを更新する
        notifyDataset(pattern)
      }

    fun notifyDataSet(pattern: ReplacePattern){
        //add,removeするFragmentに対応したpositionを指定
        if (pattern == ReplacePattern.A){
            notifyItemRemoved(0)
            notifyItemInserted(0)
        }
        if (pattern == ReplacePattern.AB){
            notifyItemRemoved(1)
            notifyItemRangeInserted(1,2)
        }
    }
}

ただ、このコードでFragment操作を実行すると以下のエラーが出ます。

java.lang.IllegalStateException: Fragment already added

これはFragmentStateAdapterがコレクションをIDで管理しているためです。
そのためFragmentを操作する場合は、以下の2つのメソッドをoverrideする必要があります。

  • getItemId(position: Int): Long
  • getItemCount(position: Int): Long

FragmentをHashCodeで管理する

IDはLongで管理しているため、FragmentのHashCodeをIDとして利用することにします。
完成系が以下になります。

ViewPagerAdapter.kt
class ViewPagerAdapter (fragment: Fragment): FragmentStateAdapter(fragment) {
    private val fragmentList = mutableListOf<Fragment>()
    private val idsList = mutableListOf<Long>()


    init {
        fragmentList.apply {
            add(FragmentOne())
            add(FragmentTwo())
            add(FragmentThree())
            //FragmentをHashCodeで管理
            forEach {
                idsList.add(it.hashCode().toLong())
            }
        }
    }

    override fun getItemCount(): Int = fragmentList.size

    override fun createFragment(position: Int): Fragment = fragmentList[position]

    //以下の2つをoverrideしないと"java.lang.IllegalStateException"が発生する
    override fun getItemId(position: Int): Long = idsList[position]

    override fun containsItem(itemId: Long): Boolean = idsList.contains(itemId)



    fun replaceFragment(pattern: ReplacePattern){

        //FragmentOneをremoveして、FragmentAに置き換える
        if (pattern == ReplacePattern.OneToA) {
            fragmentList.removeFirst()
            fragmentList.add(0, (FragmentA()))
        }

        //FragmentTwoをremoveして、FragmentA , Bに置き換える
        if (pattern == ReplacePattern.TwoToAB){
            fragmentList.removeAt(1)
            fragmentList.add(1, (FragmentA()))
            fragmentList.add(2, (FragmentB()))
        }

        //idを更新
        idsList.clear()
        fragmentList.forEach {
            idsList.add(it.hashCode().toLong())
        }

        notifyDataSet(pattern)
    }

    private fun notifyDataSet(pattern: ReplacePattern){
        if (pattern == ReplacePattern.OneToA){
            notifyItemRemoved(0)
            notifyItemInserted(0)
        }
        if (pattern == ReplacePattern.TwoToAB){
            notifyItemRemoved(1)
            notifyItemRangeInserted(1,2)
        }
    }
}

後はViewPagerAdapter#replaceFragment()をよしなに呼び出せば期待した動作が実行されます。

今回作成したサンプルアプリ↓

参考

3
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
1