FragmentPagerAdapterをほぼ無限スクロールに対応させる

2016.05.17

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

はじめに

ViewPagerの無限スクロールは、色々な方がライブラリを提供しています。

しかし、PagerTabStripと併用できるものが見当たらないため、作成しました。

実装

InfiniteViewPager.java

public class InfiniteViewPager extends ViewPager {

    InfinitePagerAdapter mInfinitePagerAdapter;

    public InfiniteViewPager(Context context) {
        super(context);
    }

    public InfiniteViewPager(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    public void setAdapter(PagerAdapter adapter) {
        mInfinitePagerAdapter = (InfinitePagerAdapter) adapter;
        super.setAdapter(adapter);
        setCurrentItem(0); // 初期表示位置
    }

    @Override
    public void setCurrentItem(int item) {
        // 第2引数をtrueにする場合、
        // アニメーションが不自然になるので、スクロールイベントでの対応が必要になる
        setCurrentItem(item, false);
    }

    @Override
    public void setCurrentItem(int item, boolean smoothScroll) {
        super.setCurrentItem(
            item % mInfinitePagerAdapter.getRealCount() + getOffsetAmount(),
            smoothScroll
        );
    }

    @Override
    public int getCurrentItem() {
        // Viewの表示などで利用されるので、値を誤魔化す
        return super.getCurrentItem() % mInfinitePagerAdapter.getRealCount();
    }

    private int getOffsetAmount() {
        // 開始位置を真ん中に近い数値にすることで、初期表示時に左へスクロールできるようにする
        // FIXME ページ数によっては、良い感じに調整してください。
        return mInfinitePagerAdapter.getRealCount() * 100;
    }
}

InfinitePagerAdapter.java

/**
 * 表示する各クラスで、実装を行えるようにする
 */
public abstract class InfinitePagerAdapter extends FragmentPagerAdapter {

    private int mRealCount = -1;

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

    /**
     * 表示するフラグメントを実装する
     */
    @Override
    public abstract Fragment getItem(int position);

    /**
     * 表示するタイトルを設定する
     */
    @Override
    public abstract CharSequence getPageTitle(int position);

    @Override
    public int getCount() {
        // return Integer.MAX_VALUE を利用しているライブラリが多いですが、
        // あまり大きすぎる値を設定すると、PagerTabStripと併用した際に
        // タブをクリックすると、フリーズしてしまう。
        return 1000;
    }

    public void setRealCount(int count) {
        mRealCount = count;
    }

    public int getRealCount() {
        return mRealCount != -1 ? mRealCount : 0;
    }

    protected int convertToDummyPosition(int realPosition) {
        return realPosition % getRealCount();
    }
}

View側の実装(適宜良い感じに)

    <com.sample.view.InfiniteViewPager
        android:id="@+id/sample_view_pager"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        >
        <android.support.v4.view.PagerTabStrip
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            />
    </com.sample.view.InfiniteViewPager>
    @Nullable
    @Override
    public View onCreateView(// 省略) {
        
        // 省略
        
        InfinitePagerAdapter adapter = new SampleAdapter(getChildFragmentManager());
        adapter.setRealCount(3); // 実際のページ数
        mViewPager.setAdapter(adapter);
        
        // 省略
    }

    // 実装クラス
    private static final class SampleAdapter extends InfinitePagerAdapter {

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

        @Override
        public Fragment getItem(int position) {
            return SampleFragment.newInstance();
        }

        @Override
        public CharSequence getPageTitle(int position) {
            // 実際には大きな値がpositionに来るので
            // ダミー値に変換する
            switch (convertToDummyPosition(position)) {
                case 0:
                    return "0番目タイトル";
                case 1:
                    return "1番目タイトル";
                case 2:
                    return "2番目タイトル";
                default:
                    return "error";
            }
        }
    }

補足

今回の実装方法では、実質無限スクロールです。

getCountにて大きい値を設定し、getOffsetAmountで表示開始位置を真ん中に近い数値にしています。

その為、何回もスライドすれば限界まで到達します。

ダミーを両サイドに用意(C' A B C A')して、スクロールイベントで調整する方法もありますが

シンプルに実装できることと、PagerTabStrip・PagerTitleStripをそのまま導入できるので、ご紹介致しました。

あとがき

需要も多いので、公式で対応してくれると嬉しいのですが・・・

app:isLoop="true" と書くだけで出来るようになって欲しいですね(笑)