본문 바로가기
Android

[Android] Multi-View Type을 지원하는 RecyclerView 구현하기

by 너츠너츠 2022. 7. 4.

배경

기존에는 하나의 RecyclerView에 같은 타입의 뷰만 띄웠지만 여러 타입의 뷰를 띄워야 하는 경우도 있습니다. 

이런 경우 RecyclewView.Adapter를 상속하는 CommonAdapter를 구현하여 Multi-View Type이 적용되도록 하는 방법을 알아보겠습니다

 

Data는 아래와 같이 viewType과 type에 따른 viewObject가 제공됩니다

{
  "viewItems": [
    {
      "viewType": "TWO_LINE_TEXT",
      "viewObject": {
        "titleText": "서울대입구역",
        "descText": "서울특별시 관악구 남부순환로 지하 1822"
      }
    },
    {
      "viewType": "ONE_LINE_TEXT",
      "viewObject": {
        "titleText": "서울대입구역"
      }
    },
    {
      "viewType": "ONE_IMAGE",
      "viewObject": {
        "imageVO": {
          "url": "https://dimg.donga.com/wps/NEWS/IMAGE/2022/01/28/111500268.2.jpg",
          "width": 1024,
          "height": 765
        }
      }
    }
  ]
}

 

구현

CommonItem.kt 

/* 모든 유형의 Item은 viewType, viewObject 형태로 제공됩니다 */
data class CommonItem(
    val viewType: String,
    val viewObject: ViewObject
)

 

ViewObject.kt

/* viewType 종류에 따라 Object를 생성해줘야 합니다 */
sealed class ViewObject {

    data class OneImageViewObject(
        val imageVO: ImageVO
    ) : ViewObject()

    data class OneLineTextViewObject(
        val contents: String
    ) : ViewObject()

    data class TwoLineTextViewObject(
        val title: String,
        val contents: String
    ) : ViewObject()

}

data class ImageVO(
    val url: String,
    val width: Int,
    val height: Int
)

 

ViewType.kt

/* 새로운 뷰타입이 생길 때마다 업데이트 해야 합니다 */
enum class CommonViewType(viewType: String) {
    ONE_LINE_TEXT("ONE_LINE_TEXT"),
    TWO_LINE_TEXT("TWO_LINE_TEXT"),
    ONE_IMAGE("ONE_IMAGE")
}

 

RecyclerView

CommonViewHolder.kt

/* 새로운 뷰타입이 생길 때마다 ViewHolder를 추가해야 합니다 */
sealed class CommonViewHolder(
    binding: ViewDataBinding
) : RecyclerView.ViewHolder(binding.root) {

    abstract fun bind(item: CommonItem)

    class OneLineTextViewHolder(
        private val binding: ItemOneLineTextBinding
    ) : CommonViewHolder(binding) {
        override fun bind(item: CommonItem) {
            val viewObject = item.viewObject as ViewObject.OneLineTextViewObject
            binding.content.text = viewObject.contents
        }
    }
    
    class TwoLineTextViewHolder(
        private val binding: ItemTwoLineTextBinding
    ) : CommonViewHolder(binding) {
        override fun bind(item: CommonItem) {
            val viewObject = item.viewObject as ViewObject.TwoLineTextViewObject
            binding.title.text = viewObject.title
            binding.content.text = viewObject.contents
        }
    }
    
    class OneImageViewHolder(
        private val binding: ItemOneImageBinding
    ) : CommonViewHolder(binding) {
        override fun bind(item: CommonItem) {
            val viewObject = item.viewObject as ViewObject.OneImageViewObject
            Glide.with(binding.root)
                .load(viewObject.imageVO.url)
                .into(binding.image)
        }
    }
}

기존의 Adapter 내부에서 Recycler.ViewHolder를 만드는 과정을 CommonViewHolder로 추출하여 따로 처리하는 과정을 거칩니다. bind의 경우 데이터가 들어왔을 때 뷰에 적용하는 과정을 각각의 ViewHolder에 맞게끔 작업하기 위해 abstract로 선언하였습니다

 

CommonAdapter.kt

class CommonListAdapter(
    private val dataSet: ArrayList<CommonItem>
) : RecyclerView.Adapter<CommonViewHolder>(){
    
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CommonViewHolder {
        /* 새로운 뷰타입이 생길 때마다 분기를 추가 */
        return when(viewType) {
            CommonViewType.ONE_LINE_TEXT.ordinal -> {
                CommonViewHolder.OneLineTextViewHolder(
                    getViewDataBinding(parent, R.layout.item_one_line_text))
            }
            CommonViewType.TWO_LINE_TEXT.ordinal -> {
                CommonViewHolder.TwoLineTextViewHolder(
                    getViewDataBinding(parent, R.layout.item_two_line_text))
            }
            else -> {
                CommonViewHolder.OneImageViewHolder(
                    getViewDataBinding(parent, R.layout.item_one_image))
            }
        }
    }

    override fun onBindViewHolder(holder: CommonViewHolder, position: Int) {
        holder.bind(dataSet[position])
    }

    override fun getItemCount(): Int = dataSet.size

    /* CommonViewType에서 해당 data의 viewType의 ordinal(인덱스)를 반환  */
    override fun getItemViewType(position: Int): Int {
        return CommonViewType.valueOf(dataSet[position].viewType).ordinal
    }

    /* binding 생성 */
    private fun <T: ViewDataBinding> getViewDataBinding(parent: ViewGroup, layoutRes: Int) : T {
        return DataBindingUtil.inflate(
            LayoutInflater.from(parent.context),
            layoutRes,
            parent,
            false
        )
    }
}

getItemViewType 함수를 통해 현재 아이템의 viewType에 해당하는 CommonViewType의 ordinal(인덱스)를 반환하며

반환된 값은 onCreateViewHolder로 연결됩니다.

 

그 이후 Factory Method 패턴을 적용하고 다른 코드들을 정리하여 최종적으로 깃허브에 올려놨습니다! 한 번 참고해주세요!!

 

 

GitHub - JGeun/Android_Study: This repository is an Android Study Collections

This repository is an Android Study Collections. Contribute to JGeun/Android_Study development by creating an account on GitHub.

github.com

 

<참고>

 

[Android/Kotlin] Multi-ViewType을 사용하는 RecyclerView의 구조를 추상화 해보기

수정일: 2021/5/12 - class 이름 변경 RecyclerView를 사용하다보면 하나의 아이템만 보여주는것이 아니라 다양한 형태의 아이템을 보여주고 싶을 때가 있습니다. 여러 타입의 아이템을 보여주는 데에는

developer-munny.tistory.com

 

반응형

댓글