無限スクロール
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