LoginSignup
1
1

[Android] ViewPager2 無限スクロール

Last updated at Posted at 2024-04-30

無限スクロール

APIを通じて画像を読み込んで、読み込んだ画像とテキストを無限スクロールできるようにするために、画像とテキストビューの位置がそれぞれ異なるため、アイテムを別々に作成して設定しました。

インジケータは読み込む画像の数に応じて設定される必要があり、TabLayoutMediatorを使用すると無限に増えてしまうため、コード内で実装する必要がありました。また、ボタンを作成して、クリックすると画像が切り替わるようにしました。

コールバックを通じてメイン画像の位置を通知し、テキストとインジケータを設定します。

APIで読み込む画像は数が増える可能性があるため、サンプルでは内部画像でビューモデルやデータクラスなどは削除されており、静的ですが、アイテムの数に応じて動的に変更し、状況に応じて修正する必要があります。

ViewPager2

MainFragment.kt

private fun setTop() {
    with(binding.viewpagerHomeBanner) {
        adapter = ImageAdapter(choonsik).apply {
            binding.viewpagerHomeBanner.orientation = ViewPager2.ORIENTATION_HORIZONTAL
        }

        val pageWidth = resources.getDimension(R.dimen.dimen_310)
        val pageMargin = resources.getDimension(R.dimen.dimen_15)
        val screenWidth = resources.displayMetrics.widthPixels
        val offset = screenWidth - pageWidth - pageMargin

        binding.viewpagerHomeBanner.offscreenPageLimit = 2

        binding.viewpagerHomeBanner.setPageTransformer { page, position ->
            page.translationX = position * -offset

        }

        // 画像の左右にスライドを表示するための画像の重ね合わせ / 切り取り設定
        val offsetBetweenPages = resources.getDimensionPixelOffset(R.dimen.dimen_30).toFloat()
        binding.viewpagerHomeBanner.setPageTransformer { page, position ->
            val myOffset = position * -(1.40f * offsetBetweenPages)
            if (position < -1) {
                page.translationX = -myOffset
            } else if (position <= 1) {

                val scaleFactor = 1.02f.coerceAtLeast(1 - abs(position))
                page.translationX = myOffset
                page.scaleY = scaleFactor
                page.alpha = scaleFactor
            } else {
                page.alpha = 0f
                page.translationX = myOffset
            }
        }

        setTopText()
        setupOnBoardingIndicators()
        setCurrentBannerIndicator(0)

        // 初期タブ位置
        currentPage = Int.MAX_VALUE / 2 - (Int.MAX_VALUE / 2) % choonsik.size
        binding.viewpagerHomeBanner.setCurrentItem(currentPage, false)

        binding.viewpagerHomeBanner.registerOnPageChangeCallback(object :
            ViewPager2.OnPageChangeCallback() {

            override fun onPageSelected(position: Int) {
                super.onPageSelected(position)

                currentPage = position
                setCurrentBannerIndicator(position)
                binding.textView.setCurrentItem(position, false)

                if (position == binding.viewpagerHomeBanner.adapter!!.itemCount - 1) {
                    binding.viewpagerHomeBanner.setCurrentItem(0, false)
                    setCurrentBannerIndicator(position)
                }
            }
        })
    }
}
  • registerOnPageChangeCallback を通じてページ選択の変更を処理し、新しいページが選択されるたびに現在のページを更新し、現在の画像表示を設定し、アイテムを変更します。
  • Text_ViewPagerc2 では、画像とテキストが合う必要があるため、位置を設定し、 Indicator 部分も関数で位置を調整して範囲内で繰り返すようにできます。

Indicator

MainFragment.kt

  private fun setupOnBoardingIndicators() {

      binding.indicators2.removeAllViews()

      // サンプルは内部で指定して行いましたが、データを受け取るときはアイテムの数を受け取って行います。
      val indicators = arrayOfNulls<ImageView>(6)

      val layoutParams = LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
          ViewGroup.LayoutParams.WRAP_CONTENT)

      layoutParams.leftMargin = 30

      for (i in indicators.indices) {
          indicators[i] = ImageView(requireActivity().applicationContext)
          indicators[i]?.setImageDrawable(ContextCompat.getDrawable(
              requireActivity().applicationContext,
              R.drawable.arrow_indicator_inactivie
          ))

          indicators[i]?.layoutParams = layoutParams

          binding.indicators2.addView(indicators[i])
      }
  }

  private fun setCurrentBannerIndicator(index: Int) {

      val childCount = binding.indicators2.childCount
      val position = index % childCount
      for (i in 0 until childCount) {
          val imageView = binding.indicators2.getChildAt(i) as ImageView
          if (i == position) {
              imageView.setImageDrawable(activity?.let {
                  ContextCompat.getDrawable(requireActivity().applicationContext,
                      R.drawable.arrow_indicator_active)
              })
          } else {
              imageView.setImageDrawable(activity?.let {
                  ContextCompat.getDrawable(requireActivity().applicationContext,
                      R.drawable.arrow_indicator_inactivie)
              })
          }
      }
  }
  • setupOnBoardingIndicators() :
    まず binding.indicators2.removeAllViews() はViewModelからデータの数を受け取り、画像を読み込む際にそのビューを更新するときに数が増える現象が発生していたため、元のビューを削除する必要がありました。

  • setCurrentBannerIndicator() :
    ViewPager2の現在の位置であるindexを引数に受け取り、
    LinearLayoutの子の数を取得し、モジュール演算子を使用して位置を計算し、位置が常に子ビューの範囲内にあるようにし、forループでLinearLayoutのすべての子を繰り返し処理します。
    ImageViewに対して現在のindex를(i)が提供された位置と同じかどうかを確認し、表示設定を行います。

Adapter

RecyclerView.Adapter

  override fun onBindViewHolder(holder: ItemViewHolder, position: Int) {
      holder.bind(items[position % items.size])
  }

  override fun getItemCount(): Int {
      return Int.MAX_VALUE
  }
  • アダプターでアイテム数をサイズとして取得するのではなく、 MAX_VALUEを使用して幻想を与えることができます。必ずしもMAX_VALUE でなくても、適切な数値を記載しても問題ありません。

GitHub : https://github.com/GEUN-TAE-KIM/InfiniteViewpager2_Indicator_Sample.git

参考
https://furang-note.tistory.com/25

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