1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

【Android】ViewPager2で無限スクロールを実現させてみたら意外と簡単だった

Posted at

はじめに

皆さん、ごきげんよう!れぶです!

今回の記事では、ViewPager2×FragmentStateAdapterで無限ループさせる方法をまとめます。

Fragment間の無限スクロール動作を実現したい方に特に参考になればと思います。それでは、参りましょう!!

開発環境

  • MacBook Air
  • Android Studio Dolphin | 2021.3.1
  • Kotlin
  • compileSdkVersion 33
  • targetSdkVersion 33
  • minSdkVersion 21

動作イメージ

今回は以下のように、縦方向のスクロールで3ページ分を無限ループします。

ezgif.com-gif-maker.gif

考え方

結論、実際のページに偽の2ページ分を追加するやり方です。あくまで実際の最初or最後のページから移動したかを判定するために行います。
Qiita_Android_ViewPager2.png

  • 実際の最初のページ(1)の前に、偽の最後のページ(FAKE3)を配置します。これによって、偽の最後のページ(FAKE3)に移動したと同時に、実際の最後のページ(3)にアニメーションなしで見た目上バレずに移動させます。

  • 実際の最後のページ(3)の後に、偽の最初のページ(FAKE1)を配置します。これによって、偽の最初のページ(FAKE1)に移動したと同時に、実際の最初のページ(1)にアニメーションなしで見た目上バレずに移動させます。

解説

ViewPager2と接続するAdapter側と、ViewPager2を表示するView側に実装します。

Adapter側の実装

ViewPagerAdapter.kt
class ViewPagerAdapter(mainActivity: MainActivity): FragmentStateAdapter(mainActivity) {

    private val realNumPage = 3

    override fun getItemCount(): Int = realNumPage + 2
    fun getRealCount() = realNumPage

    override fun createFragment(position: Int): Fragment {
        return when(position) {
            0,3 -> ThirdFragment()
            1,4 -> FirstFragment()
            2 -> SecondFragment()
            else -> ThirdFragment()
        }
    }
}

ViewPager2で読み込むページ数は、実際のページ数+2に設定します
あとは、位置がずれないようにFragmentを配置すればOKです。

View側の実装

MainActivity.kt
override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        //ViewPager2の設定
        viewPager2 = findViewById(R.id.viewPager2)
        viewPagerAdapter = ViewPagerAdapter(this)
        viewPager2.apply {
            adapter = viewPagerAdapter
            setCurrentItem(1,false)
            offscreenPageLimit = 1
            registerOnPageChangeCallback(callBack)
        }
    }

private val callBack = object : OnPageChangeCallback() {
        private var realPosition = -1
        override fun onPageScrollStateChanged(state: Int) {
            super.onPageScrollStateChanged(state)
            //最初と最終ページの場合、無限ループになるように該当ページへ移動
            if (state == ViewPager2.SCROLL_STATE_IDLE && realPosition >= 0) {
                viewPager2.setCurrentItem(realPosition, false)
                realPosition = -1
            }
        }
        override fun onPageSelected(position: Int) {
            super.onPageSelected(position)
            //最初と最終ページの場合、実際の位置を変数realPositionに代入
            when (position) {
                0 -> realPosition = viewPagerAdapter.getRealCount()
                viewPagerAdapter.getRealCount() + 1 -> realPosition = 1
            }
        }
    }

肝は、ViewPager2にコールバックを追加し、ページ状況を逐一管理してあげることです。

onPageSelected()で、最初or最後のページが選択された場合に、それぞれ移動する位置を変数に入れます。onPageScrollStateChanged()で、最初or最後のページに到達した場合に、その変数の位置にアニメーションなしで見た目上バレずに移動させます。彼らはあくまで偽物で、実際の表示用ではないためです。

ViewPager2の初回読み込み時に、setCurrentItem()で1番目の位置に設定することを忘れずに行えば完了です。

サンプルコード

補足

実際の最初or最後のページで動画等の重め処理を行う際は、それらのページが一瞬固まることもあります。その場合は以下二つで回避できます。

  • offscreenPageLimitで、ViewPager2で一度に読み込むページ数の設定を変更する
  • ViewPager2に配置するFragmentの順番を変更する

実務で発見しました。以上余談でした。

おわりに

今回はViewPager2で無限スクロールを実現する方法を整理しました。実際の最初or最後のページから移動したかを判定するためだけに偽の2ページを両端に追加するという考え方が理解できれば、容易に実装できるのではないでしょうか。

ユーザのスクロール操作の負担を少しでも軽くするために、この記事が少しでも役立つと嬉しいです。以上です。ありがとうございました!

参考サイト

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?