以前、iOS の UIScrollView で、ページつき無限スクロールを実現する という記事を書いて、Android ではどうするのかな、と思いました。少し調べてみたところ、Looping/Infinite ViewPager という記事がドンピシャでした。英語記事ということと、データの更新については触れられていない(少しユースケースが異なる)、ということでここにまとめてみます。
アイデア
ViewPager で、5ページ分のスペースを用意しておきます。
[0][1][2][3][4]
- 最初は1ページ目から始めます。
- 0ページ目にたどり着いたら、3ページ目に飛びます。
- 4ページ目にたどり着いたら、1ページ目に飛びます。
こうすることで、5ページ分のスペースの端っこに落ち着くことはないので、常に左右にスクロールできる状態になります。
この時、データの更新についても考えなくてはいけません。上記のステップ 2. で 0ページ目から 3ページ目に飛んだ時、現在のデータから後ろ三つを削除し、前三つを補填します。
同様にステップ 3. で 4ページ目から 1ページ目に飛んだ時、現在のデータから前三つを削除し、後ろ三つを補填します。
コード
PagerAdapter サブクラスの実装
コールバックの実装はいたって普通です。ページ総数は 5 なので、getCount()
では 5 を返しています。
private var dataList = ... // データリスト
...
override fun isViewFromObject(view: View, `object`: Any): Boolean {
return view == `object`
}
override fun getCount(): Int {
return 5
}
override fun instantiateItem(container: ViewGroup, position: Int): View {
val view = ... // Layout から inflate
val dataItem = dataList.get(position)
// set dataItem to the view
view.setData(data.get(position))
container.addView(view)
return view
}
override fun destroyItem(container: ViewGroup, position: Int, `object`: Any) {
container.removeView(`object` as View)
}
次にデータの更新のために次のカスタムメソッドを用意しておきます。
fun initializeData() {
dataList.clear()
for (i in 0 until 5) {
dataList.add(...) // 初期データの設定
}
}
fun forwardData() {
for (i in 0 until 3) {
imageList.removeAt(0)
}
for (i in 0 until 3) {
imageList.add(...) // 後データの補填
}
}
fun rewindData() {
for (i in 0 until 3) {
imageList.removeAt(5 - i - 1)
}
for (i in 0 until 3) {
imageList.add(0, ...) // 前データの補填
}
}
Activity (もしくは Fragment) 側の実装
// adapter は PagerAdapter サブクラスのインスタンス
viewPager.adapter = adapter
// listener は ViewPager.OnPageChangeListener サブクラスのインスタンス。詳細は後述。
viewPager.addOnPageChangeListener(listener)
// 最初は 1ページ目から始める。
viewPager.setCurrentItem(1, false)
listener は ViewPager.OnPageChangeListener
サブクラスのインスタンスで、この実装の肝です。ページが変わったというイベントをキャッチして、必要に応じて別のページに飛ぶという実装をここで行います。
val listener: ViewPager.OnPageChangeListener = object: ViewPager.OnPageChangeListener {
private var jumpPosition = -1;
override fun onPageScrolled(
position: Int,
positionOffset: Float,
positionOffsetPixels: Int
) {
// 何もしない
}
override fun onPageSelected(position: Int) {
if (position == 0) {
jumpPosition = 3
adapter.rewindData() // 先ほど用意した、前データの補填 method
} else if (position == 4) {
jumpPosition = 1;
adapter.forwardData() // 先ほど用意した、後データの補填 method
}
}
override fun onPageScrollStateChanged(state: Int) {
//アニメーションが終わるのを待ってから飛ぶ。
// onPageSelected(int position) でやると、スクロールアニメーションがキャンセルされてしまう。
if (state == ViewPager.SCROLL_STATE_IDLE && jumpPosition >= 0) {
view!!.viewPager.setCurrentItem(jumpPosition, false);
jumpPosition = -1;
}
}
}
結果
このように、永遠にスクロールし続ける画面を作れます。
まとめと考察
iOS で UIScrollView
を使った時は、すべてのスクロールイベントを捉えてページをアジャストすることができたので、用意するページは 3ページで済みました。が、今回 ViewPager
では、捉えられるのはページにスナップした時のイベントのみで、スクロール途中のイベントを受け取れないため、5ページが必要。iOS でも UIPageViewController
なんかを使うと、まったく同じような発想で 5ページが必要になるんだと思います。
記事の内容を含んだソースコードはこちらです。