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などを改良することで表示中のページの削除や追加を実装しました。
よかったら使ってみてください。