안드로이드에서 양옆으로 슬라이드해서 화면이 바뀌는 것들을 자주 봤을 것이다!
요런거
안드로이드에서는 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" />
아직은 추가해도 화면엔 아무것도 보이지 않는다.
viewPager 는 안에 여러 화면 (fragment, view) 들을 갖고있는 객체인데, 안에 들어갈 화면을 만들어주는 작업이 필요하다.
main_item_view.xml
캐릭터의 이미지와 이름, 좋아하는 것들을 소개하는 viewPager 를 만들어볼 거시다.
화면의 틀은 같고 내용은 다르기 때문에 viewPager 안에 여러 fragment 를 넣는 방식이 아닌, 하나의 fragment 를 넣되 내용물만 바꿔끼는 방식으로 구현할 예정!
ImageView 에 들어갈 사진의 id 값과 TextView 에 들어갈 문구들이 필요해서 클래스로 만들어버렸다.
class Profile(
val image: Int,
val name: String,
val favorite: String
)
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 에 데이터를 표시한다. 알맞는 위치에 데이터를 뿌려주는 역할!
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 로 설정한다.
결과는
카카오 친구들이 퍼레이드한다. 😭 쏘큩
viewPager2 에서 추가적인 처리를 해주고 싶을때, Callback 리스너를 사용하면 된다.
abstract fun onPageScrollStateChanged(state: Int): Unit
로그를 찍어 확인해보면,
SCROLLSTATE_DRAGGING -> SCROLL_STATE_SETTLING -> SCROLL_STATE
IDLE 순서로 호출된다!
onPageScrollStateChanged 에서는 scroll 상태에 따라 처리를 하는 부분이라고 생각하면 될 듯하다!
abstract fun onPageScrolled( position: Int, positionOffset: Float, @Px positionOffsetPixels: Int ): Unit
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() 로 등록하면 된다.
전체 코드는 여기! 에서 확인 가능합니당.