Android + ViewPager2で特定のページ(ポジション)を最初に表示する


概要

ViewPager2を使ってカレンダーの実装をしていたのですが、みなさんもご存知だと思うのですがカレンダーは先の月だけではなく前の月もスクロールしたら見れますよね?
それを実装するためにはViewPagerで真ん中辺りのページを開く必要があったのでその方法を記載します。

試してだめだったこと

ViewPager2について調べてみると、currentItemなるものがあり、そちらが現在のItem(ページ)を示すみたいだったので、そこの値をかえればいいのでは?と思い

MainActivity.kt
    private lateinit var binding: ActivityMainBinding

    override fun onCreate(savedInstanceState: Bundle?) {
        binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
        super.onCreate(savedInstanceState)
//        setContentView(R.layout.activity_main)

        val pager = ScreenSlidePagerAdapter(this)
        binding.pager.adapter = pager
        //ここで指定
        binding.pager.currentItem = 4
    }

    private inner class ScreenSlidePagerAdapter(fa: FragmentActivity) : FragmentStateAdapter(fa) {
        override fun getItemCount(): Int = Int.MAX_VALUE

        override fun createFragment(position: Int): Fragment = CalendarFragment(position)
    }

一旦このように実装してみたのですが、起動してみると一番最初のページスタートになってしまいました...

成功した例

そこで色々調べてみると、ただcurrentItemの値を返るだけではだめなようで、オプションも設定する必要があるとのこと。
https://stackoverflow.com/questions/56311862/viewpager2-default-position
ここの記事では時間を開けて処理を実行してるのですが、おそらくそれはする必要がなく

        binding.pager.setCurrentItem(4, false)

こんな感じで特定のページを指定してあげるだけで良さそう。

実行結果

昨日の記事を読んでいた方にはわかると思うのですが、月を表示するようにしたところ10月スタートになりました。(ただこれでは困るので6月の状態で途中スタートにしたい)

(+α)月の表示調節

カレンダーはほぼ無限スクロールなので開始位置はIntのMAX_VALUEの半分スタートにします。

MainActivity.kt
binding.pager.setCurrentItem(Int.MAX_VALUE / 2, false)

そしてフラグメントの方で今月(6月)を真ん中に表示しつつ月のラベルを渡すには

CalendarFragment.kt
        val default = Int.MAX_VALUE / 2
        binding = FragmentCalendarBinding.inflate(inflater, container, false)
        inflater.inflate(R.layout.fragment_calendar, container, false)
        val calendar = Calendar.getInstance()
        val year = 2019
        val month = 6
        val minDay = 1
        calendar.set(year, month, 1)
        calendar.add(Calendar.MONTH, position - default)
        val maxDay = calendar.getActualMaximum(Calendar.DAY_OF_MONTH)
        val blankDays = calendar.get(Calendar.DAY_OF_WEEK)

        val dayArray: MutableList<String> = mutableListOf()
        for (i in minDay until blankDays) {
            dayArray.add("")
        }
        for (i in minDay..maxDay) {
            dayArray.add(i.toString())
        }
        binding.date = dayArray.toTypedArray()
        binding.monthData = (((month - 1 + (position - default)) % 12) + 1).toString() + "月"

        return binding.root

こんな感じに書けばよいかと。

最終的な実行結果

左右にスクロールでき、ちゃんと月も正しい値になりました!!