無限Swipeを実装してみる(ViewPager編)

無限にSwipeできる画面ってどうやって実装しているのかなぁと考えたのが事の発端。無限だったり循環だったりどっちでもいいんだけど、左はじまでいったら右はじに戻る奴だったり何個表示するかわからないものをSwipeで実装したりする場合を考えてみる。ぱっと思いつくのは以下の2つ。

  1. FrameLayoutで次のやつをずらしたりして頑張る
  2. ViewPagerでページ数をほぼ無限にする

ほんとうの意味で無限といえるのはFrameLayoutでtouchイベント拾ってScrollイベントをOverrideするやつなんだろうけど、数が増えるとLayoutの管理を自分で実装しないとメモリをばかみたいに食うことになるから手間がかかる。ということで、今回はViewPagerを使ってさっくりと組んでみた。

MainActivity.java

public class Main extends FragmentActivity {

    private InfinateSwipeViewPagerAdapter mPagerAdapter;
    private ViewPager mViewPager;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        mPagerAdapter = new InfinateSwipeViewPagerAdapter(getSupportFragmentManager());

        mViewPager = (ViewPager)findViewById(R.id.viewpager);
        mViewPager.setAdapter(mPagerAdapter);
    }

    @Override
    protected void onResume() {
        super.onResume();
        mViewPager.setCurrentItem(InfinateSwipeViewPagerAdapter.MAX_PAGE_NUM/2);
    }
}

InfinateSwipeViewPagerAdapter.java

public class InfinateSwipeViewPagerAdapter extends FragmentPagerAdapter {

    public static final int MAX_PAGE_NUM = 1000;
    private static final int OBJECT_NUM = 3;

    public InfinateSwipeViewPagerAdapter(FragmentManager fm) {
        super(fm);
    }

    @Override
    public Fragment getItem(int position) {
    	Fragment fragment;
    	int diff = (position - (MAX_PAGE_NUM / 2)) % OBJECT_NUM;
    	int index = (0 > diff) ? (OBJECT_NUM + diff) : diff;
    	switch(index) {
    	case 0:
    		fragment = new FirstFragment();
    		break;
    	case 1:
    		fragment = new SecondFragment();
    		break;
    	case 2:
    		fragment = new ThirdFragment();
    		break;
    	}
        return  fragment;
    }

    @Override
    public int getCount() {
        return MAX_PAGE_NUM;
    }
}

解説

今回の例は3つのFargmentが循環する例。ポイントは以下の3点。

  1. PagerAdapterのgetCountでそんなにSwipeできないだろーって数を与える(*1)。
  2. MainActivityのonResumeでPositionを真ん中に持ってくることで最初から左にもSwipeできるようにする。
  3. Positionは初期値をonResume()で変えてるからgetItem()で渡されるpositionを元に初期値からどれだけ移動したかを計算してInstanceを返答する。

注意点

実装する上で気をつけなければならないのは以下の3点。

  1. getItem()で返答するInstanceはPositionごとに別のものを返さなければならない。そうしないとAndroidが怒って落ちちゃう。開放をAndroidがやってくれてるからだめなんだと推測される。
  2. 毎回NewしてるからNewするコストがかかる。
  3. 循環しているとはいえ実際は別のInstanceが返答されてるから前に変更した値が循環して表示した時には初期値に戻る。

まとめ

ViewPagerでやるとAndroidが勝手に表示しているViewの左右1つを作ってくれてそれ以外は開放してくれるからメモリ管理がひたすら楽。循環じゃない場合はindexをFragmentのコンストラクタに渡してごちゃごちゃやってやればうまくいくんじゃないかと。気が向いたらFrameLayoutで実装してみるかも。まぁ、使えるところはCalenderとかそのへんだろうから、わざわざFrameLayoutとかで実装しないでViewPagerでやるほうが無難だと思う。

*1: Integer.MAX_VALUEとか本当はやりたいんだけど、それやるとViewPagerが許容してくれなくてバグるみたいだから適当な値を入れる。