ViewPagerのページ数を動的に変更するためのライブラリを作りました。(https://github.com/takaaki7/ArrayPagerAdapter
イチから実装するのが結構面倒だからです。(後述)
ArrayFragmentStatePagerAdapter
, ArrayFragmentPagerAdapter
, ArrayViewPagerAdapter
の3つのアダプターが使えます。
ListView
などのシンプルな利用に使われるandroid.widget.ArrayAdapter<T>
を参考にしました。
使い方
ArrayFragmentStatePagerAdapter<T>
の使い方の例です。
@Override
protected void onCreate(Bundle savedInstanceState) {
/** ... **/
adapter = new MyStatePagerAdapter(getSupportFragmentManager()
, new String[]{"1", "2", "3"});
((ViewPager)findViewById(R.id.view_pager)).setAdapter(adapter);
adapter.add("4");
adapter.remove(0);
}
class MyStatePagerAdapter extends ArrayFragmentStatePagerAdapter<String> {
public MyStatePagerAdapter(FragmentManager fm, String[] datas) {
super(fm, datas);
}
@Override
public Fragment getFragment(String item, int position) {
return MyFragment.newInstance(item);
}
}
アダプターに対しadd(String item)
やremove(int position)
を呼べば細かいことは気にせずともデータを動的に増減させることができ、それに対応していらないページの削除などを内部でよしなにやってくれます。
ArrayFragmentPagerAdapter
とArrayViewPagerAdapter
も同じような方法で使えます。
詳しくは
https://github.com/takaaki7/ArrayPagerAdapter
のデモなどをご覧ください。
なぜ
なぜ動的なページの増減が難しいのか。
おさらいとして、ViewPager
を使う場合、PagerAdapter
、FragmentPagerAdapter
、FragmentStatePagerAdapter
の3種類のアダプターを使う方法があります。
例えばFragmentStatePagerAdapter
はこのように利用すると思います。
public static class MyAdapter extends FragmentStatePagerAdapter {
public MyAdapter(FragmentManager fm) {
super(fm);
}
@Override
public int getCount() {
return NUM_ITEMS;
}
@Override
public Fragment getItem(int position) {
return MyFragment.newInstance(position);
}
}
しかし、動的にページを増減させる場合次のようなことも意識しないけいけません。
1 int getItemPosition(Object object)
を実装する
getItemPosition
はViewPager
から呼ばれるメソッドです。これはobject
(フラグメントとか)の位置が変わった場合は新しい位置を返し、変わってない場合はPOSITION_UNCHANGED
を返し、object
が既にない場合はPOSITION_NONE
を返す必要があります。
ViewPager
のコードを見ると、notifyDataSetChanged()
が呼ばれた時に現在保持している全object
をmAdapter.getItemPosition(object)
に渡して呼び出し、POSITION_NONE
や新しい位置が返ってきた場合はページを作り直したり位置を計算し直したりしているようです。
デフォルトのPagerAdapter
たちは動的なデータの増減を想定して作られていないので常時POSITION_UNCHANGED
を返しています。
(メソッド名が若干わかりにくい。。。)
2.1 FragmentStatePager
の不具合
詳しくはコメント欄に投稿しましたが、FragmentStatePager
の場合はgetItemPosition(object)
でif(object == 消したいフラグメント){ return POSITION_NONE } else { objectの位置 }
のような感じでgetItemPosition()
を実装するだけでは他のページとの整合性がうまく取れず、表示がおかしくなる場合があるようです。おそらくFragmentStatePager
の不具合によるものです。
2.2 FragmentPagerAdapter
の場合、一度追加したFragment
を取り除かなければならない
FragmentPagerAdapter
の内部では一度追加したFragment
のremove()
を行っておらず、ページが隠れて破棄される時はdetach()
(UIを削除する)をしているだけなのでFragmentManager
の管理下でメモリに残ります。正しく削除するためにdetach()
ではなくremove()
をしたいところです。
どうやって
getItemPosition()
で常時POSITION_NONE
を返すようにしたり、表示する内容を変更した時にアダプターを再度作ってセットし直す方法とかもあるようですが、そうすると毎回全て一からインスタンス化し直すことになるのでエコじゃないと思います。
ArrayPagerAdapter
は外から渡されたデータをリストとして保存し管理することでgetItemPosition
を正しく実装し、元のFragmentStatePagerAdapter
やFragmentPagerAdapter
などを改良することで表示中のページの削除や追加を実装しました。
よかったら使ってみてください。