ViewPager2
기존 ViewPager 라이브러리의 개선된 버전으로, ViewPager 를 사용 시 발생하는 일반적인 문제를 해결해줍니다.
Android 공식 문서에서도 ViewPager2 사용을 권장하고 있습니다.
🚨 ViewPager2 의 이점
- 세로 방향 지원 (Orientation 속성을 활용하여 Horizontal Paging 에서 Vertical Paging 도 지원)
- 오른쪽에서 왼쪽 지원 (LayoutDirection 속성을 활용하여 RT(Right To Left) 페이징 지원)
- 수정 가능한 프래그먼트 컬랙션 (notifyDatasetChanged() 를 호출하여 UI 업데이트 지원)
- DiffUtil (RecyclerView 기반으로 빌드되므로 DiffUtil 유틸리티 클래스 액세스 가능. 애니메이션 활용 가능)
https://developer.android.com/training/animation/vp2-migration?hl=ko
🚨 ViewPager2 사용하기
https://developer.android.com/guide/navigation/navigation-swipe-view-2?hl=ko
💡 작성한 파일 목록 입니다.
1. build.gradle(:app)
2. SliderEntity.kt
3. activity_slider_main.xml
4. SliderMainActivity.kt
5. fragment_slider.xml
6. SliderRecyclerAdapter.kt
7. SliderPagerAdapter.kt
8. SliderPagerFragment.kt
결과 화면
ViewPager2 를 이용한 가로 방향 페이징과 세로방향 페이징 결과 화면입니다.
1. build.gradle(:app)
dependencies 안에 ViewPager2 에 대한 의존성을 추가해줍니다.
implementation 'androidx.viewpager2:viewpager2:1.0.0' //viewPager2 추가
...
dependencies {
...
//viewPager2
implementation 'androidx.viewpager2:viewpager2:1.0.0'
...
}
2. SliderEntity.kt
ViewPager 에 표시할 리스트의 data model 클래스입니다.
package com.eun.myappkotlin02.function
data class SliderEntity (
var imgSrc: Int?,
var imgName: String?,
)
3. activity_slider_main.xml
SliderMainActivity.kt 에 대한 레이아웃 리소스입니다.
가로방향 슬라이딩과 세로방향 슬라이딩을 모두 표시하도록 구현하였습니다.
아이템 리스트가 있는 경우 ViewPager2 인 view_pager_horizontal, view_pager_vertical 를 표시하도록,
리스트가 없는 경우 tv_empty_horizontal, view_pager_vertical 를 표시하도록 하였습니다.
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<TextView
android:id="@+id/tv_title"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:layout_marginBottom="10dp"
android:text="ViewPager Activity"
android:textSize="25sp"
android:textColor="@color/black"
android:textStyle="bold"
android:gravity="center"
android:background="@color/amber_200"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toTopOf="@id/tv_ori_horizontal"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
<TextView
android:id="@+id/tv_ori_horizontal"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:layout_marginHorizontal="10dp"
android:text="방향 : 가로"
android:textSize="20sp"
android:textStyle="bold"
android:gravity="center"
android:background="@drawable/bg_custom_button_purple"
app:layout_constraintTop_toBottomOf="@id/tv_title"
app:layout_constraintBottom_toTopOf="@id/view_pager_horizontal"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
<androidx.viewpager2.widget.ViewPager2
android:id="@+id/view_pager_horizontal"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_marginBottom="10dp"
android:layout_marginHorizontal="15dp"
android:background="@color/blue_200"
android:orientation="horizontal"
app:layout_constraintTop_toBottomOf="@id/tv_ori_horizontal"
app:layout_constraintBottom_toTopOf="@id/tv_ori_vertical"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
<TextView
android:id="@+id/tv_empty_horizontal"
android:layout_width="wrap_content"
android:layout_height="0dp"
android:layout_marginBottom="10dp"
android:layout_marginHorizontal="15dp"
android:gravity="center"
android:background="@color/blue_200"
android:text="가로방향 ViewPager2 리스트가 존재하지 않습니다."
app:layout_constraintTop_toBottomOf="@id/tv_ori_horizontal"
app:layout_constraintBottom_toTopOf="@id/tv_ori_vertical"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
<TextView
android:id="@+id/tv_ori_vertical"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:layout_marginHorizontal="10dp"
android:text="방향 : 세로"
android:textSize="20sp"
android:textStyle="bold"
android:gravity="center"
android:background="@drawable/bg_custom_button_purple"
app:layout_constraintTop_toBottomOf="@id/view_pager_horizontal"
app:layout_constraintBottom_toTopOf="@id/view_pager_vertical"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
<androidx.viewpager2.widget.ViewPager2
android:id="@+id/view_pager_vertical"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_marginBottom="10dp"
android:layout_marginHorizontal="15dp"
android:background="@color/green_200"
android:orientation="vertical"
app:layout_constraintTop_toBottomOf="@id/tv_ori_vertical"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
<TextView
android:id="@+id/tv_empty_vertical"
android:layout_width="wrap_content"
android:layout_height="0dp"
android:layout_marginBottom="10dp"
android:layout_marginHorizontal="15dp"
android:gravity="center"
android:background="@color/green_200"
android:text="세로방향 ViewPager2 리스트가 존재하지 않습니다."
app:layout_constraintTop_toBottomOf="@id/tv_ori_vertical"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
4. SliderMainActivity.kt
가로방향으로 슬라이드되는 형태는 RecyclerView.Adapter 를 사용한 ViewPager2 로 구현(initViewForHorizontal) 하였고,
세로방향으로 슬라이드되는 형태는 FragmentStateAdapter 를 사용한 ViewPager2 로 구현(initViewForVertical) 하였습니다.
자동 슬라이딩을 위해 handler 와 runnable 을 사용하였습니다.
package com.eun.myappkotlin02.function
import android.os.Bundle
import android.os.Handler
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.isGone
import androidx.core.view.isVisible
import androidx.viewpager2.widget.ViewPager2
import com.eun.myappkotlin02.R
import com.eun.myappkotlin02.databinding.ActivitySliderMainBinding
class SliderMainActivity : AppCompatActivity() {
lateinit var binding: ActivitySliderMainBinding
private lateinit var mSliderPagerAdapter: SliderPagerAdapter
private lateinit var mSliderRecyclerAdapter: SliderRecyclerAdapter
private var sliderList: MutableList<SliderEntity> = mutableListOf()
/* viewPagerHorizontal 에 사용돠는 handler, runnable */
private val hHandler = Handler()
private val hRunnable =
Runnable {
binding.viewPagerHorizontal.currentItem = binding.viewPagerHorizontal.currentItem + 1
}
/* viewPagerVertical 에 사용돠는 handler, runnable */
private val vHandler = Handler()
private val vRunnable =
Runnable {
binding.viewPagerVertical.currentItem = binding.viewPagerVertical.currentItem + 1
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivitySliderMainBinding.inflate(layoutInflater)
setContentView(binding.root)
initView()
}
private fun initView() {
// 리스트 데이터 세팅
sliderList.add(0, SliderEntity(R.drawable.img_blue, "img_blue"))
sliderList.add(1, SliderEntity(R.drawable.img_mint, "img_mint"))
sliderList.add(2, SliderEntity(R.drawable.img_mix, "img_mix"))
sliderList.add(3, SliderEntity(R.drawable.img_pink, "img_pink"))
sliderList.add(4, SliderEntity(R.drawable.img_purple, "img_purple"))
sliderList.add(5, SliderEntity(R.drawable.img_skyblue, "img_skyblue"))
sliderList.add(6, SliderEntity(R.drawable.img_white, "img_white"))
sliderList.add(7, SliderEntity(R.drawable.img_yellow, "img_yellow"))
initViewForHorizontal()
initViewForVertical()
}
/**
* initViewForHorizontal()
* RecyclerView.Adapter 를 사용한 ViewPager2 구현
*/
private fun initViewForHorizontal() {
if(sliderList.size > 0) {
binding.viewPagerHorizontal.isVisible = true
binding.tvEmptyHorizontal.isGone = true
mSliderRecyclerAdapter = SliderRecyclerAdapter()
binding.viewPagerHorizontal.adapter = mSliderRecyclerAdapter
binding.viewPagerHorizontal.orientation = ViewPager2.ORIENTATION_HORIZONTAL
mSliderRecyclerAdapter.setSliderList(this, binding.viewPagerHorizontal, sliderList)
binding.viewPagerHorizontal.registerOnPageChangeCallback(object :
ViewPager2.OnPageChangeCallback() {
override fun onPageSelected(position: Int) {
super.onPageSelected(position)
hHandler.removeCallbacks(hRunnable)
hHandler.postDelayed(hRunnable, 2000) // Slide duration 2 seconds
}
})
} else { // sliderList 가 없는 경우 viewPager2 hidden 처리
binding.viewPagerHorizontal.isInvisible = true
binding.tvEmptyHorizontal.isVisible = true
}
}
/**
* initViewForVertical()
* FragmentStateAdapter 를 사용한 ViewPager2 구현
*/
private fun initViewForVertical() {
if(sliderList.size > 0) {
binding.viewPagerVertical.isVisible = true
binding.tvEmptyVertical.isGone = true
mSliderPagerAdapter = SliderPagerAdapter(this)
binding.viewPagerVertical.adapter = mSliderPagerAdapter
binding.viewPagerVertical.orientation = ViewPager2.ORIENTATION_VERTICAL
mSliderPagerAdapter.setSliderList(sliderList)
binding.viewPagerVertical.registerOnPageChangeCallback(object :
ViewPager2.OnPageChangeCallback() {
override fun onPageSelected(position: Int) {
super.onPageSelected(position)
vHandler.removeCallbacks(vRunnable)
vHandler.postDelayed(vRunnable, 2000) // Slide duration 2 seconds
}
})
} else { // sliderList 가 없는 경우 viewPager2 hidden 처리
binding.viewPagerVertical.isInvisible = true
binding.tvEmptyVertical.isVisible = true
}
}
override fun onResume() {
super.onResume()
hHandler.postDelayed(hRunnable, 2000) // viewPagerHorizontal 2초마다 Slide
vHandler.postDelayed(vRunnable, 2000) // viewPagerVertical 2초마다 Slide
}
override fun onPause() {
super.onPause()
hHandler.removeCallbacks(hRunnable) // viewPagerHorizontal 2초마다 Slide
vHandler.removeCallbacks(vRunnable) // viewPagerVertical 2초마다 Slide
}
}
5. fragment_slider.xml
SliderRecyclerAdapter.kt 와 SliderPagerFragment.kt 에 대한 레이아웃 리소스입니다.
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="10dp"
android:background="@android:color/transparent" >
<ImageView
android:id="@+id/item_img"
android:layout_width="match_parent"
android:layout_height="150dp"
android:padding="10dp"
android:gravity="center"
tools:src="@drawable/ic_launcher_bear"
android:background="@color/orange_200"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toTopOf="@id/item_name"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintVertical_chainStyle="packed"/>
<TextView
android:id="@+id/item_name"
android:layout_width="match_parent"
android:layout_height="50dp"
android:text="1. yellow"
android:textSize="15sp"
android:gravity="center"
android:background="@color/pink_200"
app:layout_constraintTop_toBottomOf="@id/item_img"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
6. SliderRecyclerAdapter.kt
가로방향으로 슬라이드되는 형태는 RecyclerView.Adapter 를 사용한 ViewPager2 로 구현하였습니다.
package com.eun.myappkotlin02.function
import android.content.Context
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import androidx.viewpager2.widget.ViewPager2
import com.bumptech.glide.Glide
import com.eun.myappkotlin02.R
import com.eun.myappkotlin02.databinding.FragmentSliderBinding
class SliderRecyclerAdapter : RecyclerView.Adapter<SliderRecyclerAdapter.ViewHolder>() {
companion object {
const val TAG = "ViewPagerAdapter"
}
private lateinit var context: Context
private lateinit var mViewPager2: ViewPager2
var pagerItems: MutableList<SliderEntity> = mutableListOf()
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
context = parent.context
return ViewHolder(FragmentSliderBinding.inflate(LayoutInflater.from(parent.context), parent, false))
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
if(pagerItems.size < 0) {
// SliderMainActivity 에서 hidden 처리되어 여기까지 안넘어옴
} else {
holder.bind(pagerItems[position], position)
if(pagerItems.size > 1) { // size 가 1보다 큰 경우(2 이상) 무한 슬라이딩
if(position == (pagerItems.size-1)) { // 무한 슬라이딩되도록 함
mViewPager2.post(runnable)
}
}
}
}
override fun getItemCount(): Int = pagerItems.size
inner class ViewHolder(private val viewBinding: FragmentSliderBinding): RecyclerView.ViewHolder(viewBinding.root) {
fun bind(item: SliderEntity, position: Int) {
Glide.with(context).load(item.imgSrc) // 이미지
.placeholder(R.drawable.ic_launcher_bear)
.error(R.drawable.ic_launcher_bear)
.into(viewBinding.itemImg)
viewBinding.itemName.text = "${position+1}. ${item.imgName}"
}
}
fun setSliderList(context: Context, viewPager2: ViewPager2, pagerItems: MutableList<SliderEntity>) {
this.context = context
this.mViewPager2 = viewPager2
this.pagerItems = pagerItems
notifyDataSetChanged()
}
private val runnable = Runnable {
pagerItems.addAll(pagerItems)
notifyDataSetChanged()
}
}
7. SliderPagerAdapter.kt
세로방향으로 슬라이드되는 형태는 FragmentStateAdapter 를 사용한 ViewPager2 로 구현하였습니다.
package com.eun.myappkotlin02.function
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
import androidx.viewpager2.adapter.FragmentStateAdapter
class SliderPagerAdapter(fragmentActivity: FragmentActivity) : FragmentStateAdapter(fragmentActivity) {
private lateinit var dataList: MutableList<SliderEntity>
private var dataSize: Int = 0
override fun getItemCount(): Int = dataSize
override fun createFragment(position: Int): Fragment {
return SliderPagerFragment().setFragment(position, dataList)
}
fun setSliderList(paramList: MutableList<SliderEntity>) {
this.dataList = paramList
this.dataSize = paramList.size
}
}
8. SliderPagerFragment.kt
SliderPagerAdapter 의 Fragment 입니다.
package com.eun.myappkotlin02.function
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import com.bumptech.glide.Glide
import com.eun.myappkotlin02.R
import com.eun.myappkotlin02.databinding.FragmentSliderBinding
class SliderPagerFragment: Fragment() {
lateinit var binding: FragmentSliderBinding
private var position: Int = 0
private var dataList : MutableList<SliderEntity> = mutableListOf()
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
binding = FragmentSliderBinding.inflate(inflater, container, false)
return binding?.root
// return inflater.inflate(R.layout.item_viewpager, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
Glide.with(this).load(dataList[position].imgSrc) // 이미지
.placeholder(R.drawable.ic_launcher_bear)
.error(R.drawable.ic_launcher_bear)
.into(binding.itemImg)
binding.itemName.text = "${position+1}. ${dataList[position].imgName}"
}
fun setFragment(paramPosition: Int, paramList: MutableList<SliderEntity>) : Fragment {
val fragment = SliderPagerFragment()
fragment.position = paramPosition
fragment.dataList = paramList
return fragment
}
override fun onDestroyView() {
super.onDestroyView()
}
}
감사합니다 🙆🏻♀️
'📱 안드로이드 Android ~ Kotlin' 카테고리의 다른 글
[Android/Kotlin] Google Maps API 사용해서 지도 표시하기 (2) | 2023.01.10 |
---|---|
[Android/Kotlin] ViewPager2 를 이용한 스크롤 시 애니메이션 적용(smoothScroll, PageTransformer) (0) | 2022.11.03 |
[Android/kotlin] 구글 Firebase Remote Config 사용하기 (0) | 2022.09.07 |
[Android/kotlin] 구글 Firebase In-App Messaging 사용하기 (0) | 2022.07.28 |
[Android/kotlin] 구글 Firebase Realtime Database 사용한 채팅 앱 만들기 (0) | 2022.06.24 |