概要

  • FragmentStatePagerAdapter のバグで悩んだのでメモ

  • ここ の #52 に書かれているように 2018/7/31 時点でもバグが残ったままみたい。#11 のを使わせてもらったら想定通りの動きになった。

  • FragmentPagerAdapter は基本的に全 Fragment を保存するらしいので、あまり調べてない

  • FragmentStatePagerAdapter は基本的には見てるページと左右の3ページのみ保存するはず

    -ViewPager#setOffscreenPageLimit() でページ数を増やした場合は、そのページ分保存してくれる

詳細

  • 問題は FragmentStatePagerAdapter#getItemPosition() で正しい position を返しても保存されるべき Fragment が detach されてしまうこと

    • [A, B] の状態で C を真ん中に入れようとすると B の Fragment の instance の position の問い合わせが来るから、2 と返すと(0 origin なので 2) 、なぜか detach されてしまう。ので、[A, C, B] のつもりが [A, C, 空] となってしまう。
  • ぐぐると、常に POSITION_NONE を返せばこのバグを回避できることがわかる。こことか。

    • でも、これだと、なくなったことを Adapter に伝えるので毎回 detach されてしまう。その後、表示ページと左右の3ページ分の getItem() が来て Fragment を生成するはめになる。

    • 本来は、左右合わせて3ページ分は Adapter 内で保存してもらいたい。(または setOffscreenPageLimit() で設定したページ分は保存してもらいたい。が、POSITION_NONE を返している場合は、これで多めに設定すると逆に detach → attach が増えて重くなる。。)

解決方法

  • ここ の #11 の SortableFragmentStatePagerAdapter を使わせてもらえば問題なさそう

    • ただし、getItemId() で unique な ID を正しく返して、getItemPosition() で正しい position か POSITION_NONE を返す必要あり。
      position が変わらない場合は POSITION_UNCHANGED を返すべきだとは思うけど、position を返しておいても問題はなさそうに見える。

    • 以下で問題なさそう

      • SortableFragmentStatePagerAdapter のサブクラスで、各ページの Fragment 情報を保持するリストを用意

      • ページを追加するときは、Fragment 情報と合わせて unique な ID も一緒にリストに追加する

      • getCount() ではそのリストのサイズを返すようにしておく

      • notifyDataSetChanged() すると、リストのサイズが変わったときは getItem() が来るので、そこでリスト内の Fragment 情報を使って Fragment 生成して、unique な ID を setArguments() で渡してから Fragment の instance を返す

      • 直後(直前か?)に、既存ページの場所問い合わせで getItemPosition(Fragmentのinstance) が来るので、Fragment 内に渡しておいた unique な ID を取得して、上記リスト内の ID と比較して position を求めて返す

      • getItemId() の使われ方はあまり調べてないが、notifyDataSetChanged() したときにページの順番が変わってないかをチェックするのに使われるはず。とりあえず、上記と同じ unique な ID を返しておけば問題なさそう。引数の position をそのまま返すだけじゃダメ。override しないのもダメ。

      • サンプルコードを書けという感じだな^^;

  • ここ のもいいかも?未確認。