Tech Log

[Android] ViewPager2 본문

Android/Widget

[Android] ViewPager2

yuhee kim 2022. 6. 23. 23:46

클론 코딩을 하면서, ViewPager2를 사용하게 되었다.
사용한 지 좀 오래 되어, 복기용으로 글을 작성하게 되었다.

0. 개념

출처 : Android Developers(https://developer.android.com/reference/kotlin/androidx/viewpager2/widget/ViewPager2?hl=ko)

ViewPager2는 화면 슬라이드에 이용되는 레이아웃 클래스이다.
ViewPager2가 ViewGroup으로부터 상속된 것을 보면, Container 역할을 한다는 사실을 유추해낼 수 있다.
단어의 뜻 그대로, View + Pager = 페이지를 쓸어 넘기듯이 View를 넘기는 것이다.
이 뷰에서는 스와이프(Swipe) 제스처를 통해서, 한 화면에서 다음 화면으로 전환할 수 있다.
ViewPager2는 데이터를 페이지 단위로 화면에 표시한다.
여러 종류의 뷰 위젯을 사용하여 ViewPager2를 구성한다.

1. ViewPager와 ViewPager2

ViewPager2라고 되어 있어서 그럼 ViewPager1이 있나? 이런 생각을 할 수 있다.
사실 ViewPager2 이전에, ViewPager라는 것이 있었다.
ViewPager에는 세로 방향 페이징 과정이 번거롭고 스크롤이 버벅거리는 현상이 있었다.
ViewPager에 있던 몇몇 기능들을 개선한 것이 ViewPager2이다.
그런데 ViewPager2도 몇몇 버그가 있는건지, ViewPager를 종종 쓰는 사람들이 있는 것 같다.
그래서 ViewPager는 아직 deprecated되지는 않았다.

ViewPager2에 추가된 기능은 다음과 같다.

  • 세로 방향 페이징 지원
  • 오른쪽에서 왼쪽 페이징 가능(Right-to-left, RTL 가능)
  • notifyDataSetChanged() 버그 해결
  • RecyclerView를 이용한 페이징 기법 사용(가장 중요)
  • DiffUtil 사용 가능


1) 세로 방향 페이징 지원

기존의 ViewPager에서는 세로 방향으로 넘기는 것이 불가능했다.
하지만 ViewPager2에서는 이 점이 개선되었다.

<androidx.viewpager2.widget.ViewPager2
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:id="@+id/pager"
        android:orientation="vertical" />


ViewPager2의 orientation 속성을 사용하여 세로 방향 페이징이 가능해졌다.
혹은 setOrientation() 메소드로 코드 레벨에서도 세로 방향 속성이 지정가능하다.


2) 오른쪽에서 왼쪽 페이징 가능(RTL 페이징 가능)

ViewPager2에서는 오른쪽에서 왼쪽(RTL) 페이징이 가능하다.

<androidx.viewpager2.widget.ViewPager2
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:id="@+id/pager"
        android:layoutDirection="rtl" />


layoutDirection 속성을 통해 페이징 방향을 오른쪽에서 왼쪽으로 설정할 수 있다.
혹은 setLayoutDirection() 메소드로 코드 레벨에서 설정가능하다.


3) notifyDataSetChanged 버그 해결

기존의 ViewPager에서 notifyDataSetChanged()가 실행되지 않는 버그가 있었다.
ViewPager2에서 이 점을 해결하여 notifyDataSetChanged() 메소드를 정상적으로 호출할 수 있게 되었다.

notifyDataSetChanged()를 호출하면 ViewPager 내의 Item이 추가, 삭제, 이동이 되었을 때 변화를 감지할 수 있다.


4) RecyclerView를 이용한 페이징 기법 사용

기존의 ViewPager는 PagerAdapter 기반으로 구성되었다. 따라서 페이징을 할 때마다 instantiateItem(), destroyItem() 메소드가 호출되어 버벅거리는 문제가 있었다.
그러나 ViewPager2에서는 PagerAdapter가 아닌, RecyclerView를 이용하게 되면서 instantiateItem(), destroyItem() 메소드를 호출할 필요가 없어졌다. RecylcerView는 이름 그대로 View를 재활용하기 때문이다. Item을 새로 생성하거나 삭제할 필요가 없어졌기 때문에 여러 메소드를 호출하지 않아도 되는 것이다.
버벅거리는 문제를 해결함과 동시에 메모리 리소스를 줄일 수 있는 장점도 더해졌다.
말했듯이 RecyclerView는 View를 재사용하기 때문이다 (이에 대한 자세한 내용은 RecyclerView에 대한 포스팅을 작성하여 다뤄볼 것이다)


5) DiffUtil 사용 가능

ViewPager2에서 RecyclerView를 이용하게 되면서 DiffUtil은 사용할 수 있게 되었다.
DiffUtil은 이전 데이터 리스트와 현재 데이터 리스트 상태 차이를 계산하여, 변화가 있을 시 최소한의 데이터에 대해서만 갱신하도록하는 클래스이다. DiffUtil에 대해서도 다뤄봐야 할 내용이 좀 있기 때문에 다른 포스팅에서 다뤄보도록 하겠다.

2. 동작 방식

ViewPager는 항상 현재 페이지를 기준으로 좌, 우 페이지와 현재 페이지 총 세 페이지를 생성 및 관리한다.

  • 페이지가 표시되는 매 순간 생성 + 미리 모든 페이지를 생성 이 두 방법을 절충
    • 생성 시간을 줄임과 동시에 메모리 절약이 가능하다

3. ViewPager2의 사용(RecyclerView 이용)


3.1 app 수준의 gradle 파일에 의존성 추가

dependencies {
	implementation 'androidx.viewpager2:viewpager2:1.0.0'
}


3.2 Activity 혹은 Fragment 레이아웃에 ViewPager2 추가

<?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"
    tools:context=".ViewPagerActivity">

    <androidx.viewpager2.widget.ViewPager2
        android:layout_width="match_parent"
        android:layout_height="500dp"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        android:id="@+id/viewpager2"
        android:clipChildren="false"
        android:clipToPadding="false"
        android:paddingStart="30dp"
        android:paddingEnd="30dp"
        android:paddingTop="30dp"
        android:paddingBottom="30dp"
        android:orientation="horizontal"
        android:layout_marginTop="20dp"/>

    <LinearLayout
        android:layout_width="0dp"
        android:id="@+id/indicators"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        android:gravity="center"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintTop_toBottomOf="@id/viewpager2"
        android:padding="15dp"
        android:layout_marginStart="20dp"/>


</androidx.constraintlayout.widget.ConstraintLayout>


3.3 ViewPager에 들어갈 Item 레이아웃 생성(view_pager_item.xml)

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center"
    android:background="#CEF279"
    android:id="@+id/background"
    android:orientation="vertical">

    <TextView
        android:id="@+id/txtCount"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="1 page"
        android:textSize="30dp"
        android:textStyle="bold"
        android:gravity="center" />

</LinearLayout>


3.4 ViewPagerAdapter 생성(VPAdapter.kt)


import android.content.Context
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.LinearLayout
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView

class VPAdapter(strData: ArrayList<String>, colorData: ArrayList<Int>)
    : RecyclerView.Adapter<VPAdapter.MyViewHolder>() {

    val arrData = strData
    val arrColors = colorData

    inner class MyViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
        var txtCount: TextView = itemView.findViewById(R.id.txtCount)
        var bgColor : LinearLayout = itemView.findViewById(R.id.background)
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
        val context = parent.context
        val inflater: LayoutInflater = context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater
        val view: View = inflater.inflate(R.layout.view_pager_item, parent, false)
        val viewHolder = MyViewHolder(view)

        return viewHolder
    }

    override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
        holder.txtCount.text = arrData[position]
        holder.bgColor.setBackgroundColor(arrColors[position])
    }

    override fun getItemCount(): Int {
        return arrData.size
    }

}

RecyclerViewAdapter(ViewPagerAdapter)에는 아래 메소드를 필수적으로 오버라이딩 한다.

  • onCreateViewHolder = ViewHolder 객체를 생성
  • onBindViewHolder = ViewHolder 에 data 를 넣는 작업 수행
  • getItemCount = data의 갯수를 반환 해준다


참고로 RecyclerView의 전체 구조는 아래 사진과 같다.

출처 : https://developer.android.com/codelabs/basic-android-kotlin-training-affirmations-app#3


Adapter

  • RecyclerView에 표시될 아이템 뷰를 생성하는 역할을 한다.
  • 사용자 데이터 리스트로부터 아이템 뷰를 만든다.


ViewHolder

  • 화면에 표시될 아이템 뷰를 저장하는 객체다.



3.5 ViewPager가 추가된 Activity 혹은 Fragment에서 ViewPager와 ViewPagerAdapter 연결

import android.graphics.Color
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import androidx.viewpager2.widget.ViewPager2

class ViewPagerActivity : AppCompatActivity() {
    private lateinit var viewPager2: ViewPager2

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_view_pager)

        var arrStr: ArrayList<String> = ArrayList()
        var arrColorsString = arrayListOf<String>("#FF0000","#FF5E00", "#FFBB00", "#FFE400", "#0054FF")
        var arrIntColor = ArrayList<Int>()
        for (i in 1..100) {
            arrStr.add(i.toString())
            arrIntColor.add(Color.parseColor(arrColorsString[i%5])) //5 단위로 색상이 반복해서 적용된다
        }

        var adapter = VPAdapter(arrStr, arrIntColor)

        viewPager2 = findViewById(R.id.viewpager2)
        viewPager2.orientation = ViewPager2.ORIENTATION_HORIZONTAL
        viewPager2.adapter = adapter

    }

}

실습 코드를 실행하면 위와 같이 된다.
(MainActivity에서 버튼으로 ViewPager를 적용한 Activity를 불렀다)

생각보다 복잡한 ViewPager 적용기다.
RecyclerView 말고 Fragment를 사용한 ViewPager 생성 방법도 있다.
이는 나중에 따로 작성해야 할 것 같다.

ViewPager는 실제로 많은 어플리케이션에서 쓰이고 있다 ex) 배달의 민족

그만큼 많이 쓰이는 위젯이므로 익혀두면 유용하게 사용할 수 있을 것 같다.

참조

'Android > Widget' 카테고리의 다른 글

[Android] Android Architecture Components(AAC)  (0) 2022.12.21
[Android] RecyclerView  (0) 2022.07.21
Comments