[Android] 뷰페이저2 무한 스크롤

KIMGEUNTAE·2023년 5월 7일
4

Android

목록 보기
2/8

무한 스크롤

이미지를 api로 불러와서 불러온 이미지와 텍스트를 무한 스크롤이 가능하게 해야 해서 이미지와 텍스트 뷰 위치 도 제각각이라 아이템을 따로 만들어서 설정을 하게 됐다.

Indicator 불러오는 이미지 숫자 만큼 설정되어야 해서 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 말고 적절하게 숫자로 적어도 괜찮음




깃허브 : https://github.com/GEUN-TAE-KIM/InfiniteViewpager2_Indicator_Sample.git


참고
https://furang-note.tistory.com/25

profile
Study Note

0개의 댓글