ViewPager2, TabLayoutを用いてタブ付きスワイプビューを作成。そのタブを無限ループさせる。

ViewPager2, TabLayoutを用いてタブ付きスワイプビューを作成する。さらにそのタブ,スワイプを無限ループできるようにする。
参考にしたのはこれら

もちろんkotlinで実装します。バージョンは1.4.21
主要な登場クラスはViewPager2, TabLayout, FragmentStateAdapter, Fragment

仕組みとしては、表示したいものが10ページあるとしたら、

[10][1][2][3][4][5][6][7][8][9][10][1]

つまりは実際のページ数に+2して、両端に実際の最後のページと最初のページをくっつけるということ。

FragmentStateAdapter側の実装

private val REAL_PAGE_SIZE = 10

class CollectionAdapter(fragment: Fragment) : FragmentStateAdapter(fragment) {

    override fun getItemCount(): Int = REAL_PAGE_SIZE + 2

    fun getRealCount() = REAL_PAGE_SIZE

    fun getRealPosition(position: Int): Int {
        return when (position) {
            0 -> getRealCount() - 1
            getRealCount() + 1 -> 0
            else -> position - 1
        }
    }

    // Return a NEW fragment instance in createFragmt
    override fun createFragment(position: Int): Fragment {
        val i = getRealPosition(position)
        return ItemFragment.newInstance("$i")
    }
}

ViewPager2,TabLayout側の実装

class BlankFragment : Fragment() {

    private var _binding: FragmentBlankBinding? = null
    private val binding get() = _binding!!

    private lateinit var collectionAdapter: CollectionAdapter
    private lateinit var viewPager: ViewPager2

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View {
        _binding = FragmentBlankBinding.inflate(inflater, container, false)
        return binding.root
    }

    override fun onDestroyView() {
        super.onDestroyView()
        _binding = null
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        collectionAdapter = CollectionAdapter(this)
        viewPager = binding.pager
        viewPager.apply {
            adapter = collectionAdapter

            registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() {

                private var realPosition = -1

                override fun onPageScrollStateChanged(state: Int) {
                    if (state == ViewPager2.SCROLL_STATE_IDLE && realPosition >= 0) {
                        viewPager.setCurrentItem(realPosition, false)
                        realPosition = -1
                    }
                }

                override fun onPageSelected(position: Int) {
                    when (position) {
                        0 -> realPosition = collectionAdapter.getRealCount()
                        collectionAdapter.getRealCount() + 1 -> realPosition = 1
                        else -> {
                        }
                    }
                }
            })
            setCurrentItem(1, false)
        }

        val tabLayout = binding.tabLayout
        tabLayout.tabMode = TabLayout.MODE_SCROLLABLE
        TabLayoutMediator(tabLayout, viewPager) { tab, position ->
            val i = collectionAdapter.getRealPosition(position)
            tab.text = "PAGE ${(i + 1)}"
        }.attach()

    }

    companion object {
        ...
    }
}

ViewPager2を持つFragmentのxml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".BlankFragment">

    <com.google.android.material.tabs.TabLayout
        android:id="@+id/tab_layout"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        />

    <androidx.viewpager2.widget.ViewPager2
        android:id="@+id/pager"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"/>

</LinearLayout>

このリポジトリ GitHub - ochim/extend-affirmationsで使っています。

以上