1. viewPager2
1-1. viewPager란?
1-2. viewPager의 활용
2. 사용 방법
2-1. 기본 사용법
2-2. 애니메이션 설정
2-3. 여백 설정
1. viewPager2
1-1. viewPager란?
페이지를 넘기듯이 이렇게 슉-슉- 넘기는 것을 viewPager(뷰 페이저)라고 한다.
원래는 사용 방법이 굉장히 복잡했는데 (내 기준에...)
2019년에 구글이 viewPager2를 발표하면서 사용하기 굉장히 쉬워졌다.
그냥 리사이클러뷰 사용하듯이 사용하면 된다.
초기에는 이런 저런 버그가 있어 개발자들이 viewPager2 보다 기존의 viewPager를 선호하는 경향이 있었는데
2년이 지난 지금 viewPager2로 넘어오지 않을 이유가 없을 것 같다.
자세한 변동사항은 위 공식문서에서 확인할 수 있다.
1-2. viewPager의 활용
뷰페이저는 다방면에서 활용이 되고 있다.
위와 같은 화면을 자주 보았을 것이다.
옆으로 스-윽 스-윽 스와이프를 해서 다음 아이템을 확인하는 기능!
리사이클러뷰를 가로로 쓰면 똑같이 구현할 수 있을 것 같지만
어느 일정 이상 페이지를 넘기면 그다음부터는 손을 떼도 자동으로 페이지가 넘어간다는 점이 다르다.
배너광고 같은 경우도 뷰페이저를 활용한 대표 예시이다.
앱 최초실행시 뜨는 소개 페이지도 뷰 페이저를 활용한 예시이다.
마지막으로 이렇게 스와이프를 통해 페이지(메뉴)를 변경하는 것도
뷰 페이저를 활용한 예시이다.
단, 이 예시는 구글 디자인 정책상 권장되지 않는 방법이다.
예전에는 카카오톡 메뉴 전환이 스와이프로 가능했었다.
어느 날 갑자기 이 기능이 막히면서 불편하다는 의견이 많이 올라오는 일이 있었는데,
자세한 설명은 위 뉴스에서 다루고 있다.
아무튼 결론은 뷰 페이저를 통한 메뉴 전환은 '하지 말아야 할 기능'이라고 구글에서 명시했다는 것!
인스타그램은 아직까지도 사용하고 있는 거 보니 마이웨이인가 보다.
2. 사용 방법
2-1. 기본 사용법
activity_main.xml
<androidx.viewpager2.widget.ViewPager2
android:id="@+id/viewPager_idol"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
사용 방법은 진짜 recyclerView랑 다를 게 없다.
우선 xml 파일에서 ViewPager2를 추가해준다.
주의할 점은 태그가 android가 아닌 androidx로 시작한다는 거!
MainActivity.kt
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
viewPager_idol.adapter = ViewPagerAdapter(getIdolList()) // 어댑터 생성
viewPager_idol.orientation = ViewPager2.ORIENTATION_HORIZONTAL // 방향을 가로로
}
// 뷰 페이저에 들어갈 아이템
private fun getIdolList(): ArrayList<Int> {
return arrayListOf<Int>(R.drawable.idol1, R.drawable.idol2, R.drawable.idol3)
}
}
어댑터를 생성할 때 아이템을 넘겨주고
방향을 가로로 해주면 메인 액티비티에서 할 일은 끝이다.
만약 방향을 xml 파일에서 설정하고 싶다면 android:orientation="vertical"을 넣어주면 된다.
ViewPagerAdapter.kt
class ViewPagerAdapter(idolList: ArrayList<Int>) : RecyclerView.Adapter<ViewPagerAdapter.PagerViewHolder>() {
var item = idolList
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = PagerViewHolder((parent))
override fun getItemCount(): Int = item.size
override fun onBindViewHolder(holder: PagerViewHolder, position: Int) {
holder.idol.setImageResource(item[position])
}
inner class PagerViewHolder(parent: ViewGroup) : RecyclerView.ViewHolder
(LayoutInflater.from(parent.context).inflate(R.layout.idol_list_item, parent, false)){
val idol = itemView.imageView_idol!!
}
}
어댑터도 리사이클러뷰랑 다를 게 없다.
이너 클래스를 쓰든 따로 파일을 만들든 그런 건 취향껏 만들면 된다.
idol_list_item.xml
<?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"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="@+id/imageView_idol"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="@drawable/idol1" />
</androidx.constraintlayout.widget.ConstraintLayout>
페이지에 띄우고 싶은 뷰를 정의하는 xml 파일을 하나 만든다.
이것도 리사이클러뷰랑 똑같다.
나는 일단 이미지 한 개만 보여주도록 만들었다.
이렇게 뷰페이저가 완성됐다!
2-2. 애니메이션 설정
공식문서를 읽어보면 페이지를 넘길 때 애니메이션을 주는 방법에 대해 적혀있다.
그래서 한 번 따라 해 보았다.
MainActivity.kt
class MainActivity : AppCompatActivity() {
private val MIN_SCALE = 0.85f // 뷰가 몇퍼센트로 줄어들 것인지
private val MIN_ALPHA = 0.5f // 어두워지는 정도를 나타낸 듯 하다.
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
viewPager_icon.adapter = ViewPagerAdapter(getIconList())
viewPager_icon.orientation = ViewPager2.ORIENTATION_HORIZONTAL
viewPager_icon.setPageTransformer(ZoomOutPageTransformer()) // 애니메이션 적용
}
private fun getIconList(): ArrayList<Int> {
return arrayListOf<Int>(R.drawable.icon1, R.drawable.icon2, R.drawable.icon3, R.drawable.icon4)
}
/* 공식문서에 있는 코드 긁어온거임 */
inner class ZoomOutPageTransformer : ViewPager2.PageTransformer {
override fun transformPage(view: View, position: Float) {
view.apply {
val pageWidth = width
val pageHeight = height
when {
position < -1 -> { // [-Infinity,-1)
// This page is way off-screen to the left.
alpha = 0f
}
position <= 1 -> { // [-1,1]
// Modify the default slide transition to shrink the page as well
val scaleFactor = Math.max(MIN_SCALE, 1 - Math.abs(position))
val vertMargin = pageHeight * (1 - scaleFactor) / 2
val horzMargin = pageWidth * (1 - scaleFactor) / 2
translationX = if (position < 0) {
horzMargin - vertMargin / 2
} else {
horzMargin + vertMargin / 2
}
// Scale the page down (between MIN_SCALE and 1)
scaleX = scaleFactor
scaleY = scaleFactor
// Fade the page relative to its size.
alpha = (MIN_ALPHA +
(((scaleFactor - MIN_SCALE) / (1 - MIN_SCALE)) * (1 - MIN_ALPHA)))
}
else -> { // (1,+Infinity]
// This page is way off-screen to the right.
alpha = 0f
}
}
}
}
}
}
기존의 코드에 SCALE, ALPHA 값을 추가하고
setPageTransformer를 뷰페이저에 적용해주고
공식문서에 있는 코드를 가져와서 붙여 넣기 한 게 전부다. 아주 심플
(ZoomOutPageTransformer는 포스팅하기 편해서 이너 클래스로 넣었다. 실제 코드에선 따로 빼는 게 보기 좋을 듯)
다소 심심했던 뷰페이저에 애니메이션 효과가 생겼다.
공식문서에 다른 애니메이션 예시가 하나 더 있고
찾아보면 다른 사람들이 만든 것도 있을 테니 기회가 되면 사용해봐야겠다.
2-3. 여백 설정
activity_main.xml
<androidx.viewpager2.widget.ViewPager2
android:id="@+id/viewPager_icon"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipToPadding="false" // 이거랑
android:clipChildren="false" // 이거 추가
/>
[1-2 viewPager의 활용] 목차에서 보여준 예시를 보면
플레이스토어에서 뷰페이저를 사용하고 있는데
다음에 올 아이템 항목이 빼꼼하고 머리를 내밀고 있는 걸 구현해보려고 한다.
이 디자인은 특별한 문구나 안내 없이도
사용자가 "확인할 아이템 항목이 더 있구나"라고 생각하게끔 만들어준다.
우선 viewPager에 새로운 옵션을 2개 넣어주었는데
그 이유는 말이 길어질 것 같아 위 링크로 대체한다.
MainActivity.kt
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
/* 여백, 너비에 대한 정의 */
val pageMarginPx = resources.getDimensionPixelOffset(R.dimen.pageMargin) // dimen 파일 안에 크기를 정의해두었다.
val pagerWidth = resources.getDimensionPixelOffset(R.dimen.pageWidth) // dimen 파일이 없으면 생성해야함
val screenWidth = resources.displayMetrics.widthPixels // 스마트폰의 너비 길이를 가져옴
val offsetPx = screenWidth - pageMarginPx - pagerWidth
viewPager_icon.setPageTransformer { page, position ->
page.translationX = position * -offsetPx
}
viewPager_icon.offscreenPageLimit = 1 // 몇 개의 페이지를 미리 로드 해둘것인지
viewPager_icon.adapter = ViewPagerAdapter()
viewPager_icon.orientation = ViewPager2.ORIENTATION_HORIZONTAL
}
}
우선... 여백과 너비를 이용하여 페이지가 어디서 얼마큼 이동할 건지를 계산한다.
이때 R.dimen.pageMargin 이 값은 따로 dimen 폴더에 정의해둔 값이므로
폴더가 없다면 values 폴더 밑에 생성해서 사용해야 한다.
이 pageMargin값과 pageWidth값은 아이템의 크기가 어느 정도인지
간격은 얼마큼으로 주고 싶은지 등등에 따라 달라지기 때문에
직접 값을 조정해가며 적당한 값을 사용하면 된다.
그리고 setPageTransformer를 이용해 page의 X축 값에 변화를 주면 끝이다.
이렇게 다음 아이템이 빼꼼 머리를 내밀고 있는
뷰페이저가 완성됐다!
참고로 offscreenPageLimit 값을 설정해두지 않으면 위와 같은 결과물이 나온다.
페이지가 넘겨질 때 그제야 다음 페이지를 로드하기 때문에
빼꼼 하지 않고 갑자기 어디서 툭! 튀어나오는 것처럼 보이는 것이다.
나는 다음 페이지가 미리 보이길 원했으므로 '1'을 넣어주었다.
만약 한 화면에 여러 개의 페이지가 보이는 상황이라면 (play store처럼)
그만큼 더 값을 늘려주면 된다.
▼ 분량이 너무 긴 관계로 <활용편>은 아래 게시물로 이어집니다 ▼
▼ 기본편, 활용편 예제 전체 코드는 깃허브에서 확인해주세요 ▼
'오늘은 뭘 배울까? > Android' 카테고리의 다른 글
안드로이드 뷰 바인딩(view binding) (6) | 2021.03.05 |
---|---|
[Kotlin] 뷰페이저2 활용 예제 : tabLayout, indicator, fragment, 자동 스크롤, 무한스크롤, 배너 등 (13) | 2021.02.27 |
안드로이드 액티비티 생명주기 (Activity Life cycle) (10) | 2021.02.23 |
콜백 함수(Callback function)란 무엇일까? (12) | 2021.02.16 |
[kotlin] 코루틴 공부하기 (비동기 처리, 서버 딜레이 처리) (12) | 2021.02.14 |
댓글