안드로이드 ViewPager2 사용하기 (DiffUtil 활용)

Ddudduu·2021년 11월 8일
3

안드로이드에서 양옆으로 슬라이드해서 화면이 바뀌는 것들을 자주 봤을 것이다!

요런거

안드로이드에서는 ViewPager2 라는 객체로 구현할 수 있다.

viewPager2 특징

  • viewPager2 는 RecyclerView 를 활용하기 때문에 구현하는 방법도 비슷하고 DiffUtil 클래스를 활용할 수 있다!
    - RecyclerView.Adpater 를 사용할 수 있다.
  • 가로 페이징 + 세로 페이징 지원 (방향 설정 가능!)
  • 왼쪽 -> 오른쪽 으로 넘기기 + 왼쪽 <- 오른쪽 넘기기 모두 가능


밑에서부터는 구현방법!

1. fragment 에 viewPager2 추가

viewPager 를 추가하고 싶은 화면에 viewPager2 객체를 추가한다.
나는 Navigation 예제를 이어서 활용할 예정이라서! main_fragment.xml 에 추가해주었다.

<androidx.viewpager2.widget.ViewPager2
      android:id="@+id/viewPager"
      android:layout_width="match_parent"
      android:layout_height="wrap_content"
      android:layout_marginHorizontal="50dp"
      android:layout_marginTop="30dp"
      android:overScrollMode="never"
      app:layout_constraintEnd_toEndOf="parent"
      app:layout_constraintStart_toStartOf="parent"
      app:layout_constraintTop_toBottomOf="@id/title" />

아직은 추가해도 화면엔 아무것도 보이지 않는다.

2. viewPager 안에 들어갈 화면 생성

viewPager 는 안에 여러 화면 (fragment, view) 들을 갖고있는 객체인데, 안에 들어갈 화면을 만들어주는 작업이 필요하다.

main_item_view.xml

캐릭터의 이미지와 이름, 좋아하는 것들을 소개하는 viewPager 를 만들어볼 거시다.

화면의 틀은 같고 내용은 다르기 때문에 viewPager 안에 여러 fragment 를 넣는 방식이 아닌, 하나의 fragment 를 넣되 내용물만 바꿔끼는 방식으로 구현할 예정!

3. Adapter 생성

3-1. Class 생성

ImageView 에 들어갈 사진의 id 값과 TextView 에 들어갈 문구들이 필요해서 클래스로 만들어버렸다.

class Profile(
  val image: Int,
  val name: String,
  val favorite: String
  )

3-2. Adapter 생성

Adapter 는 아까 만들어놓은 틀 (화면) 에 데이터들을 차곡차곡 넣어주는 역할을 한다.
내가 만든 화면의 경우,
1. ImageView 2. TextView1 3. TextView2
세 개의 위젯이 있는데 어떤 데이터를 이미지에 넣고, 어떤 데이터를 텍스트뷰에 넣을 건지 등을 정하는 부분이라고 생각하면 된다.

DiffUtil 클래스를 활용했는데, 이것도 따로 정리해놓아야겠다..! 여기선 생략!


MainViewAdpater.kt

class MainViewAdapter : ListAdapter<Profile, MainViewAdapter.MainItemViewHolder>(MainListDiffCallback) {
  private lateinit var binding: MainItemViewBinding

  override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MainItemViewHolder {
    binding = MainItemViewBinding.inflate(LayoutInflater.from(parent.context), parent, false)
    return MainItemViewHolder(binding)
  }

  override fun onBindViewHolder(holder: MainItemViewHolder, position: Int) {
    holder.bind(getItem(position))
  }

  inner class MainItemViewHolder(private val binding: MainItemViewBinding) : RecyclerView.ViewHolder(binding.root) {
    fun bind(item: Profile) {
      binding.image.setImageResource(item.image)
      binding.name.text = item.name
      binding.favorite.text = item.favorite
    }
  }

  object MainListDiffCallback : DiffUtil.ItemCallback<Profile>() {
    override fun areItemsTheSame(oldItem: Profile, newItem: Profile): Boolean {
      return oldItem == newItem
    }

    override fun areContentsTheSame(oldItem: Profile, newItem: Profile): Boolean {
      return oldItem.name == newItem.name
    }

  }
}

가장 먼저 할 일은,
inner class MainItemViewHolder 을 만드는 일이다.
main_item_view 의 위젯들과 인자로 전달한 데이터(위에 코드에선 item: Profile) 를 연결짓는 작업이다. 즉, 화면과 데이터를 연결하는 역할.

override 함수들을 살펴보면, RecyclerView 를 만들때와 유사하다.
viewPager2 는 RecyclerView 를 상속받기 때문에 당연함 😙

onCreateViewHolder : main_item_view.xml 를 View Holder 를 연결하고 View Holder 를 생성한다.
onBindViewHolder : 특정 position 에 데이터를 표시한다. 알맞는 위치에 데이터를 뿌려주는 역할!

4. viewPager 와 adapter 연결하기

4-1. viewPager 에 들어갈 데이터 생성

viewPager 는 main_fragment.xml 에 선언해놓았으니
MainFragment.kt 로 가서

 profileList = mutableListOf(
      Profile(R.drawable.chunsik, "춘식이", "고구마"),
      Profile(R.drawable.angmond, "앙몬드", "쪼꼬렛"),
 )

데이터를 먼저 만들어줌.
위에서 만든 Profile 클래스로 리스트를 만들어주었다.

mainViewAdapter = MainViewAdapter()
mainViewAdapter.submitList(profileList)
binding.viewPager.adapter = mainViewAdapter

아까 만든 Adapter 를 선언하고,
adapter 에 데이터(리스트)를 넣어준다.
그리고, viewPager 의 adpater 를 내가 생성한 mainViewAdapter 로 설정한다.


결과는

카카오 친구들이 퍼레이드한다. 😭 쏘큩

5. Callback 리스너

viewPager2 에서 추가적인 처리를 해주고 싶을때, Callback 리스너를 사용하면 된다.

  1. abstract fun onPageScrollStateChanged(state: Int): Unit
    scroll state 가 변경되면 호출된다. 사용자가 드래그를 시작할 때, pager 가 자동으로 현재 페이지로 설정될때, scroll 이 멈출때 등을 알아낼 때 사용하면 유용하다!

    scroll state 에는 세가지가 있다.
  • SCROLL_STATE_IDLE
    pager 가 멈춰있는 상태. 현재 페이지가 화면에 꽉차게 보이고 진행 중인 animation 이 없을 때를 의미한다.
  • SCROLL_STATE_DRAGGING
    사용자가 pager 를 드래그하는 상태.
  • SCROLL_STATE_SETTLING
    pager 가 다른 position 으로 설정되는 상태.

로그를 찍어 확인해보면,
SCROLLSTATE_DRAGGING -> SCROLL_STATE_SETTLING -> SCROLL_STATE
IDLE
순서로 호출된다!

onPageScrollStateChanged 에서는 scroll 상태에 따라 처리를 하는 부분이라고 생각하면 될 듯하다!

  1. abstract fun onPageScrolled( position: Int, positionOffset: Float, @Px positionOffsetPixels: Int ): Unit
    현재 페이지가 스크롤 됐을때 호출된다.
    인자 중에 position 은 몇번째 페이지인지 나타내준다. (0부터 시작!)
  1. abstract fun onPageSelected(position: Int): Unit
    새로운 페이지가 선택됐을때 호출된다.

onPageScrollStateChanged() 는 스크롤 상태가 변경되면 호출되기 때문에 맨 처음에는 호출되지않는다.
그러나 onPageScrolled(), onPageSelected() 는 viewPager 가 화면에 보여졌을때부터 호출된다!


private val pageChangeCallback = object : ViewPager2.OnPageChangeCallback() {
    override fun onPageScrollStateChanged(state: Int) {
      super.onPageScrollStateChanged(state)
      Log.w("$$$0 ", "$state")
    }

    override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) {
      super.onPageScrolled(position, positionOffset, positionOffsetPixels)
      Log.w("$$$1 ", "$position")
      Log.w("$$$2 ", "$positionOffset")
      Log.w("$$$3 ", "$positionOffsetPixels")
    }

    override fun onPageSelected(position: Int) {
      super.onPageSelected(position)
      Log.w("$$$4 ", "$position")
    }
  }

콜백 함수는 다음과 같이 interface 의 함수들을 override 하고,

binding.viewPager.registerOnPageChangeCallback(pageChangeCallback)

registerOnPageChangeCallback() 로 등록하면 된다.



전체 코드는 여기! 에서 확인 가능합니당.

profile
Android

0개의 댓글