화면 구현하다보면 뷰페이져의 사용이 한 번 쯤은 필요하기 마련이다. RecyclerViewAdapter 처럼 하나의 샘플 뷰를 만들어 두고, 각 View에 데이터를 연결하는 방식으로 동작한다. 어렵지는 않은데, 막상 세팅하려고 하면 정리가 안 돼서 정리해 보는 정리글.



ViewPager

ViewPager는 화면을 페이지와 같이 좌우로 넘길 때 사용되며, 페이지의 생명주기를 관리하기 위해 Fragment 와 함께 쓰이는 경우가 많다. 사실, 2019 Google I/O 에서는 (아마도) 업그레이드 버전인 ViewPager2 가 공개되었다. 새로운 뷰페이져2의 주요 특징은 다음과 같다.

  • RecyclerView 에 기반했다 - PagerAdapter 대신 RecyclerView.Adapter 를 사용할 수 있다.
  • RTL (Right-to-Left) 레이아웃을 지원한다.
  • LayoutManager 에서 세로방향 페이징을 지원한다.
  • 프래그먼트의 교체가 쉬워졌다.
  • 클래스 이름이 그다지 멋지지 않다.

아직 베타 버전이기도 하고, 참고 자료도 부족해보인다. 베이직한 기능만을 사용하고자 한다면 기존 ViewPager로도 충분할 것이다.



아주 간단한 ViewPager 예제

이미지와 문자열 몇개를 가진 아주 간단한 예제를 만들었다. 지금 가장 먹고 싶은 과일 순서.


순서

  1. 레이아웃 구현
  2. 모델 클래스 생성
  3. Adapter 생성
  4. MainActivity에서 연결


1. 레이아웃 구현

<!-- activity_main.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"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity"
    android:background="#efefef">

    <androidx.viewpager.widget.ViewPager
        android:id="@+id/mViewPager"
        android:layout_width="0dp"
        android:layout_height="0dp"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"/>

</androidx.constraintlayout.widget.ConstraintLayout>


<!-- layout_fruit.xml -->
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout 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">

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@android:color/white">

        <TextView
            android:id="@+id/tvItemTitle"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginLeft="16dp"
            android:layout_marginTop="16dp"
            android:textColor="#666666"
            android:textSize="28sp"
            android:textStyle="bold"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            tools:text="Item Title" />

        <ImageView
            android:id="@+id/ivItem"
            android:layout_width="0dp"
            android:layout_height="0dp"
            android:layout_margin="16dp"
            app:layout_constraintDimensionRatio="1"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toBottomOf="@id/tvItemTitle" />

        <TextView
            android:id="@+id/tvItemContent"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_margin="16dp"
            android:textColor="#666666"
            android:textSize="20sp"
            app:layout_constraintTop_toBottomOf="@id/ivItem" />

    </androidx.constraintlayout.widget.ConstraintLayout>
</FrameLayout>


2. 모델 클래스 생성

과일 이름, 이미지파일 이름, 내용을 가지고 있는 모델 클래스를 만들었다. banana01.jpg 와 같은 패키지 내 drawable 리소스를 가져오기 위해 getImageId 함수도 포함시켰다.

class Fruit(
    var name: String,
    var image: String,
    var content: String
) {
    fun getImageId(context: Context): Int {
        return context.resources.getIdentifier(image, "drawable", context.packageName)
    }
}


간단한 예제니까 Data 는 MainActivity 에서 들고 있기로 하자.

/* MainActivity.kt */

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        ...
    }

    companion object {
        val fruitList = arrayListOf(
            Fruit("Banana", "banana", "A banana is an edible fruit – botanically a berry – produced by several kinds of large herbaceous flowering plants in the genus Musa."),
            Fruit("Strawberry", "strawberry", "The garden strawberry is a widely grown hybrid species of the genus Fragaria, collectively known as the strawberries, which are cultivated worldwide for their fruit."),
            Fruit("Tangerine", "tangerine", "The tangerine is a group of orange-colored citrus fruit consisting of hybrids of mandarin orange."),
            Fruit("Plum", "plum", "A plum is a fruit of the subgenus Prunus of the genus Prunus.")
        )
    }
}

3. Adapter 생성

class FruitPagerAdapter(private val list: ArrayList<Fruit>): PagerAdapter() {

    override fun instantiateItem(container: ViewGroup, position: Int): Any {
        val inflater = LayoutInflater.from(container.context)
        val view = inflater.inflate(R.layout.layout_fruit, container, false)

        view.tvItemTitle.text = list[position].name
        view.ivItem.setImageResource(list[position].getImageId(container.context))
        view.tvItemContent.text = list[position].content

        container.addView(view)
        return view
    }

    override fun destroyItem(container: ViewGroup, position: Int, obj: Any) {
        container.removeView(obj as View?)
    }

    override fun isViewFromObject(view: View, obj: Any): Boolean {
        return view == obj
    }

    override fun getCount(): Int {
        return list.size
    }
}


4. MainActivity에서 연결

/* MainActivity.kt */
class MainActivity : AppCompatActivity() {

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

        val adapter = FruitPagerAdapter(fruitList)
        mViewPager.adapter = adapter
    }

    companion object {
        val fruitList = arrayListOf(
            Fruit("Banana", "banana", "A banana is an edible fruit – botanically a berry – produced by several kinds of large herbaceous flowering plants in the genus Musa."),
            Fruit("Strawberry", "strawberry", "The garden strawberry is a widely grown hybrid species of the genus Fragaria, collectively known as the strawberries, which are cultivated worldwide for their fruit."),
            Fruit("Tangerine", "tangerine", "The tangerine is a group of orange-colored citrus fruit consisting of hybrids of mandarin orange."),
            Fruit("Plum", "plum", "A plum is a fruit of the subgenus Prunus of the genus Prunus.")
        )
    }
}


References

  • https://developer.android.com/training/animation/screen-slide
  • https://developer.android.com/reference/androidx/viewpager2/widget/ViewPager2
  • https://proandroiddev.com/look-deep-into-viewpager2-13eb8e06e419
  • https://hyogeun-android.tistory.com/entry/ViewPager2%EC%97%90-%EA%B4%80%ED%95%9C-%EA%B3%A0%EC%B0%B0
  • https://www.journaldev.com/10096/android-viewpager-example-tutorial