본문 바로가기

Android

[Android] Multi-ViewType RecyclerView

이전 포스트에서 RecyclerView에 대해 알아보았다. 이전 포스트 내용에서는 한가지 틀을 재활용하여 연속적으로 사용하는 뷰를 구성해보았다. 그렇다면 여러가지 틀을 연속적으로 사용하는 뷰를 만들고자 한다면 어떻게 해야할까?

그럴 때 사용하는 것이 ConcatAdapter와 Multi-ViewType RecyclerView이다.

 

그렇다면 ConcatAdapter와 Multi-ViewType RecycleView의 차이점은 무엇일까?

ConcatAdapter은 역할에 확정을 지어주어야 한다. (헤더, 바디 등등) 반면 Multi-ViewType RecycleView는 보다 유연하게 사용 가능하다. 그러나 ConcatAdapter가 구현이 훨씬 용이해서 상황에 따라 선택해서 구현하는 것을 좋다.

 

 이번 포스팅에서는 Multi-ViewType RecyclerView에 대해 자세히 알아보자.

 

멀티뷰타입 리사이클러뷰를 만들기 위해서는 다음과 같은 과정이 필요하다.

  • 리사이클러뷰가 들어갈 액티비티나 프래그먼트 생성
  • 리사이클러뷰에 들어갈 다양한 아이템 생성
  • 뷰 타입 선언
  • 데이터 클래스 생성
  • 각각 아이템의 뷰홀더 생성
  • 어답터 생성

먼저 리사이클러뷰를 액티비티 안에 만들어 보자.

<androidx.recyclerview.widget.RecyclerView
               android:id="@+id/rv_wish"
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:layout_marginBottom="112dp"
                android:layout_marginHorizontal="14dp"
                android:paddingTop="16dp"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintEnd_toEndOf="parent"
                app:layout_constraintTop_toBottomOf="@id/tv_wish_my_wish"
                tools:listitem="@layout/item_wish_large" />

다음으로 리사이클러뷰에 들어갈 아이템들을 만들어보자

<?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="150dp"
    android:layout_height="200dp"
    android:background="@drawable/wish_item_rectangle">

    <androidx.appcompat.widget.AppCompatImageView
        android:id="@+id/iv_item_wl"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:layout_marginHorizontal="38dp"
        android:layout_marginTop="29dp"
        android:layout_marginBottom="100dp"
        android:background="@color/Gray_100"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <TextView
        android:id="@+id/tv_item_wl_title"
        style="@style/Body2"
        android:layout_width="wrap_content"
        android:layout_height="40dp"
        android:layout_marginTop="10dp"
        android:gravity="center"
        android:text="요리 해주기"
        android:textColor="@color/Gray_600"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/iv_item_wl" />

    <androidx.appcompat.widget.AppCompatTextView
        android:id="@+id/tv_item_wl_color"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:background="@drawable/wish_ment_green_rectangle"
        android:paddingHorizontal="16dp"
        android:paddingVertical="4dp"
        android:text="@string/wish_coupon_green"
        android:textColor="@color/Green_600"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/tv_item_wl_title" />

</androidx.constraintlayout.widget.ConstraintLayout>

 

<?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="wrap_content"
    android:layout_height="wrap_content"
    android:background="@drawable/wish_item_rectangle">


    <androidx.appcompat.widget.AppCompatImageView
        android:id="@+id/iv_item_ws"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:layout_marginHorizontal="57dp"
        android:layout_marginTop="24dp"
        android:layout_marginBottom="58dp"
        android:background="@color/Gray_100"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.0"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_bias="0.0" />

    <androidx.appcompat.widget.AppCompatTextView
        android:id="@+id/tv_item_wl_color"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="8dp"
        android:background="@drawable/wish_ment_blue_rectangle"
        android:paddingHorizontal="16dp"
        android:paddingVertical="4dp"
        android:text="@string/wish_coupon_blue"
        android:textColor="@color/Lightblue_600"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/iv_item_ws" />

</androidx.constraintlayout.widget.ConstraintLayout>

이렇게 하면 다음과 같이 리사이클러뷰에 들어갈 아이템이 생성된다.

 

다음으로 뷰 타입을 선언하자.

package sopt.uni.data.entity.wish

const val MULTI_TYPE_WISH1 = 0
const val MULTI_TYPE_WISH2 = 1

각각의 아이템에 대해 뷰타입을 만들어준다. 이를 통해 아이템을 추가할 때 어떤 타입에 아이템을 가져올지 정할 수 있다.

다음으로 데이터 클래스를 만들어보자.

package sopt.uni.data.entity.wish

data class WishMultiData(
    val type: Int,
    val image: Int,
    val title: String?,
    val color: String,
)

만들어둔 아이템들의 데이터 클래스를 만들어준다. 또한 type에 따라 다른 ViewHolder를 지정해줘야 하기 때문에 data class에 type도 추가로 만들어준다.

다음으로 각각 아이템의 뷰 홀더를 만들어보자.

package sopt.uni.presentation.wish

import androidx.recyclerview.widget.RecyclerView
import coil.load
import sopt.uni.data.entity.wish.WishMultiData
import sopt.uni.databinding.ItemWishSmallBinding

class MultiViewWishHolder1(private val binding: ItemWishSmallBinding) :
    RecyclerView.ViewHolder(binding.root) {
    fun bind(item: WishMultiData) {
        with(binding) {
            ivItemWs.load(item.image)
            tvItemWlColor.text = item.color
        }
    }
}
package sopt.uni.presentation.wish

import androidx.recyclerview.widget.RecyclerView
import coil.load
import sopt.uni.data.entity.wish.WishMultiData
import sopt.uni.databinding.ItemWishLargeBinding

class MultiViewWishHolder2(private val binding: ItemWishLargeBinding) :
    RecyclerView.ViewHolder(binding.root) {
    fun bind(item: WishMultiData) {
        with(binding) {
            ivItemWl.load(item.image)
            tvItemWlTitle.text = item.title
            tvItemWlColor.text = item.color
        }
    }
}

뷰홀더 (VeiwHolder)

레이아웃 매니저가 제공하는 레이아웃 형태. 어댑터를 통해 만들어진 각 아이템뷰는 뷰홀더 객체에 저장되어 화면에 표시되고 필요에 따라 생성 또는 재활용(Recycler)된다.

뷰홀더는 화면에 표시될 아이템 뷰를 저장하는 객체

. 어댑터에 의해 관리되는데 필요에 따라 어댑터에서 생성된다.(레이아웃매니저의 아이템 뷰 재활용 기준에 따라) 물론, 미리 생성된 뷰홀더 객체가 있으면 새로 생성하지 않고 이미 만들어진 뷰홀더를 재활용한다. 이 때

데이터는 오직 뷰홀더의 아이템 뷰에 Binding

된다

뷰 홀더에서는 만들어둔 데이터들과 xml의 ui 들과 묶어줄 수 있다.

다음으로 어답터를 생성하자.

package sopt.uni.presentation.wish

import android.content.Context
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import sopt.uni.data.entity.wish.MULTI_TYPE_WISH1
import sopt.uni.data.entity.wish.WishMultiData
import sopt.uni.databinding.ItemWishLargeBinding
import sopt.uni.databinding.ItemWishSmallBinding

class WishMultiviewAdapter(private val context: Context) :
    RecyclerView.Adapter<RecyclerView.ViewHolder>() {

    var datas = mutableListOf<WishMultiData>()

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
        return when (viewType) {
            MULTI_TYPE_WISH1 -> {
                val binding =
                    ItemWishSmallBinding.inflate(LayoutInflater.from(parent.context), parent, false)
                MultiViewWishHolder1(binding)
            }

            else -> {
                val binding =
                    ItemWishLargeBinding.inflate(LayoutInflater.from(parent.context), parent, false)
                MultiViewWishHolder2(binding)
            }
        }
    }

    override fun getItemCount(): Int = datas.size

    override fun getItemViewType(position: Int): Int {
        return datas[position].type
    }

    override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
        when (datas[position].type) {
            MULTI_TYPE_WISH1 -> {
                (holder as MultiViewWishHolder1).bind(datas[position])
                holder.setIsRecyclable(false)
            }

            else -> {
                (holder as MultiViewWishHolder2).bind(datas[position])
                holder.setIsRecyclable(false)
            }
        }
    }
}

어댑터 (Adapter)

데이터 목록을 아이템 단위의 뷰로 구성하여 화면에 표시

하기 위해 사용

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

어댑터 정의할 때는 세가지 메서드를 override 해야한다.

  • onCreateViewHolder(ViewGroup parent, int viewType) : RecyclerView는 ViewHolder를 새로 만들 때마다 이 메서드를 호출. 뷰홀더와 그에 연결된 view를 생성하지만 뷰의 내용을 채우지는 않는다. (ViewHolder가 아직 특정 데이터와 바인딩하기 전이기 때문에)
  • viewType에 해당하는 ViewHolder를 생성하여 return
  • onBindViewHolder(ViewHolder holder, int position) : 리사이클러뷰는 ViewHolder를 데이터와 연결할 때 이 메서드를 호출. 적절한 데이터를 가져와서 뷰 홀더의 레이아웃을 채운다.
  • 어댑터가 해당 position에 해당하는 데이터를 결합
  • getItemCount() : 리사이클러뷰는 데이터 세트 크기를 가져올 때 이 메서드를 호출한다. 예를 들어 주소록 앱에서는 총 주소 갯수를 세는 것.
  • 전체 아이템 개수 return

다음으로 리사이클러뷰에 적용해보자.

private fun initRecyclerView() {
        multiviewAdapter = WishMultiviewAdapter(this) {
            onClickWish()
        }

        binding.rvWish.adapter = multiviewAdapter

        binding.rvWish.apply {
            layoutManager = StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.VERTICAL)
            adapter = multiviewAdapter
        }

        datas.apply {
            add(WishMultiData(MULTI_TYPE_WISH1, null, null, "green"))
            add(WishMultiData(MULTI_TYPE_WISH2, image = R.drawable.ic_apple, "맥북 사주기", "blue"))
            add(WishMultiData(MULTI_TYPE_WISH2, image = R.drawable.ic_apple, "맥북 사주기", "blue"))
            add(WishMultiData(MULTI_TYPE_WISH2, image = R.drawable.ic_apple, "맥북 사주기", "blue"))
            add(WishMultiData(MULTI_TYPE_WISH2, image = R.drawable.ic_apple, "맥북 사주기", "blue"))
            add(WishMultiData(MULTI_TYPE_WISH2, image = R.drawable.ic_apple, "맥북 사주기", "blue"))
            add(WishMultiData(MULTI_TYPE_WISH2, image = R.drawable.ic_apple, "맥북 사주기", "blue"))
            add(WishMultiData(MULTI_TYPE_WISH2, image = R.drawable.ic_apple, "맥북 사주기", "blue"))
        }
        multiviewAdapter.submitData(datas)
        updateRecyclerViewVisibility()
    }
  • 레이아웃 매니저 (Layout Manager)
    아이템뷰가 나열되는 형태를 관리하기 위한 요소
    어댑터에서 아이템 뷰를 생성하기 이전에 어떤 형태로 배치될 아이템 뷰를 만들지 결정. 안드로이드 SDK에서는 아래 레이아웃 매니저가 기본으로 제공된다.
    • LinearLayoutManager : Horizontal OR Vertical 방향 Linear로 아이템 뷰 매치
    • GridLayoutManager : 바둑판 모양 Grid 형태로 아이템 뷰 배치
    • StaggeredGridLayoutManager : 엇갈린 Grid 형태로 아이템 뷰 배치 가능

이렇게 하면 멀티뷰타입 리사이클러뷰를 구현할 수 있다.

 

 

 

 

 

 

'Android' 카테고리의 다른 글

[Android] AAC (Android Architecture Components)  (0) 2023.08.30
[Android] DataBinding  (0) 2023.08.30
[Android] RecyclerView  (1) 2023.08.03
[Android] Fragment 생명주기  (1) 2023.04.20
[Android] Activity 생명주기와 상태  (0) 2023.04.07