Androidでカルーセルを実装してみた

qnote Advent Calendar 18日目
今回は自分でViewPagerを使ってカルーセルを実装したことについて書いてみようかと思います。

#まずライブラリに頼りたい
自分で実装する前にいい感じのライブラリがあればそれで解決だよね〜
と探してみましたが良いものが無く、自分で実装することに
それっぽいものは結構あったのですが、最終的に目的に一致するものがありませんでした。。。

#ではどうしよう？
Androidでカルーセルっぽい動きをしているのはViewPagerやViewFlipperがありますが、ViewPagerを使うことにしました。
何故かと言うとViewPagerの方が使い慣れていたらから。

#実装
さて自分で実装するにあたりヒントが欲しくていろいろ調べていたら、やはりもう実装されている方がいて公開していただけていたので、それを参考にしました。
参考URL
https://github.com/alphamu/LoopingViewPagerDemo
https://groups.google.com/forum/#!topic/android-group-japan/uqpL94Q-MUQ

上のはIndicatorの参考に
下のは実際にviewをループさせるように見せる部分の参考にしています。
無限にループするのではなく、頑張ればいつか終点に着くようになっています。
自動で切り替えるのは、簡単に自分で実装しました。
タイマーを付けて時間が来たら次のページへ移動するというだけ

全部をここに載せるのは見くくなると思ったのでgithubにあげました。
良ければ御覧ください
https://github.com/Hatijan/AndroidCarousel

#解説

キーになるのはこの部分
スタート地点を全体のカウントの真ん中に持ってきていること
実際ただそれだけのViewPagerです

```LoopedViewPager.java
public void initialize(Context context, int pages, LoopedViewPagerListener listener) {
        if (context == null || listener == null) {
            throw new RuntimeException();
        }

        mPages = pages;

        if (pages == 0) {
            return;
        }

        if (pages == 1) {
            mAdapterPages = 1;
        } else {
            mAdapterPages = ALL_PAGE_COUNT;
        }

        mListener = listener;
        setAdapter(new MyPagerAdapter());
        addOnPageChangeListener(new MyOnPageChangeListener());

        int maxSets = ALL_PAGE_COUNT / mPages;
        mFirstPos = (maxSets / 2) * mPages;
        setCurrentItem(mFirstPos);
    }
```

使い方はこんな感じ

```MainActivity.java
public class MainActivity extends AppCompatActivity {
    private List<CarouselContentView> frameLayouts;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        LayoutInflater inflater = LayoutInflater.from(this);
        frameLayouts = new ArrayList<>();
        CarouselContentView contentView = (CarouselContentView) inflater.inflate(R.layout.carousel_content, null);
        contentView.setTitle("一枚目");
        contentView.setBackgroundColor(Color.RED);
        frameLayouts.add(contentView);

        CarouselContentView contentView2 = (CarouselContentView) inflater.inflate(R.layout.carousel_content, null);
        contentView2.setTitle("二枚目");
        contentView2.setBackgroundColor(Color.BLUE);
        frameLayouts.add(contentView2);

        CarouselContentView contentView3 = (CarouselContentView) inflater.inflate(R.layout.carousel_content, null);
        contentView3.setTitle("三枚目");
        contentView3.setBackgroundColor(Color.GREEN);
        frameLayouts.add(contentView3);

        CarouselContentView contentView4 = (CarouselContentView) inflater.inflate(R.layout.carousel_content, null);
        contentView4.setTitle("四枚目");
        contentView4.setBackgroundColor(Color.YELLOW);
        frameLayouts.add(contentView4);

        LoopedViewPager carouselView = (LoopedViewPager) findViewById(R.id.pager);
        carouselView.initialize(this, frameLayouts.size(), new LoopedViewPager.LoopedViewPagerListener() {
            @Override
            public View OnInstantiateItem(int page) {
                return frameLayouts.get(page);
            }

            @Override
            public void onPageScrollChanged(int page) {
            }
        });

        LoopPageIndicator pageIndicator = new LoopPageIndicator(this, (LinearLayout) findViewById(R.id.pages_container), carouselView, R.drawable.indicator_circle);
        pageIndicator.setPageCount(frameLayouts.size());
        pageIndicator.show();

        carouselView.startMoveTimer();
    }

    @Override
    protected void onStart() {
        super.onStart();
        ((LoopedViewPager) findViewById(R.id.pager)).startMoveTimer();

    }

    @Override
    protected void onPause() {
        super.onPause();
        ((LoopedViewPager) findViewById(R.id.pager)).stopMoveTimer();
    }
}

```

```activity_main.xml
<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context="exsample.yamada.mycarousel.MainActivity">

    <FrameLayout
        android:id="@+id/carousel_view"
        android:layout_width="match_parent"
        android:layout_height="300dp"
        android:layout_centerHorizontal="true">

        <exsample.yamada.mycarousel.LoopedViewPager
            android:id="@+id/pager"
            android:layout_width="match_parent"
            android:layout_height="300dp"/>

        <LinearLayout
            android:id="@+id/pages_container"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_gravity="bottom"
            android:layout_marginBottom="8dp"
            android:gravity="center_horizontal"
            android:orientation="horizontal"/>

    </FrameLayout>
</RelativeLayout>
```

レイアウトで直接LoopedViewPagerを置いています。
内部に表示するコンテンツを生成して、LoopedViewPagerの初期化そしてタイマーを起動すればカルーセルの完成
内部のコンテンツにタップアクションを付けたい場合はそのまま、CarouselContentViewにOnClickListenerをセットすればOK

```MainActivity.java
        LayoutInflater inflater = LayoutInflater.from(this);
        frameLayouts = new ArrayList<>();
        CarouselContentView contentView = (CarouselContentView) inflater.inflate(R.layout.carousel_content, null);
        contentView.setTitle("一枚目");
        contentView.setBackgroundColor(Color.RED);
        contentView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                
            }
        });
        frameLayouts.add(contentView);
```

#まとめ

実際にはCollapsingToolbarLayoutに入れてスクロールすると縮んでいくというUIにしたのですが、なぜかALL_PAGE_COUNTにInteger.MAX_VALUEを入れるとページが切り替わらない程度にフリックするとスタート地点に戻ろうとする動き(右にいくつかページを移して、軽くフリックして離すと一枚左に動く)が発生して、ものすごくハマりました。
ALL_PAGE_COUNTを8000000としたら正しく動くようになって、何だそりゃとなりました。
8000000でも十分大きいので問題ないですが、なんで発生するかは分からずじまい。