[Android / Kotlin] Multi-View RecyclerView

Subeen·2024년 1월 11일
0

Android

목록 보기
39/73

리사이클러뷰가 멀티뷰 타입을 지원한다는 것을 알게 되어 간단한 샘플을 만들어 내용을 이해해 보려 한다. 멀티뷰 타입을 적용하면 한 개의 리스트에서 여러 유형의 아이템을 보여줄 수 있으므로 여러 개의 리스트를 만들지 않아도 돼서 많이 유용할 것 같다 ! 🤗

결화 화면

29CM 앱의 MY 탭을 클론해봤다 ! 나의 쇼핑정보주문배송조회 부분을 두 개의 뷰로 나눠서 만들었다. 🤓

구현 방법

  • sealed classenum class 등을 사용하여 다른 뷰 유형을 정의한다.
  • Adapter class에서 getItemViewType 함수를 오버라이드하여 각 아이템에 대한 뷰 유형을 결정한다.
  • onCreateViewHolder에서 뷰 유형에 따라 다른 뷰 홀더를 생성한다.
  • onBindViewHolder에서 생성된 해당 뷰에 대한 데이터를 바인딩 해준다.

Sealed class

Sealed class란?

sealed 클래스는 자기 자신이 추상 클래스이고, 자신을 상속 받는 여러 서브 클래스들을 가질 수 있다. 이를 사용하면 enum 클래스와 달리 상속을 지원하기 때문에, 상속을 활용한 풍부한 동작을 구현할 수 있다.
또한, 자신을 상속 받는 서브 클래스의 종류를 제한 할 수 있다.

  • sealed 클래스의 서브 클래스들은 반드시 같은 파일 내에 선언 되어야 한다.
  • sealed 클래스의 서브 클래스를 상속한 클래스들은 같은 파일 내에 없어도 된다.
  • sealed 클래스는 기본적으로 추상 클래스이다.
  • sealed 클래스는 private 생성자만 갖게 된다.
sealed class Items {
    data class TitleItem(val title: String) : Items()
    data class ContentItem(val content: String) : Items()
}

RecyclerView Adapter

  • onCreateViewHolder가 호출 되기 전 getItemViewType(position: Int): Int 함수가 먼저 호출되어 리턴 값이 넘겨진다. 그러므로 해당 함수에서 ViewType을 구분하여 리턴해준다.
  • 뷰타입에 해당하는 ViewHolder를 여러 개 만들고 onCreateViewHolder에서 데이터에 따라 그에 해당하는 ViewHolder를 생성해준다.
class ItemAdapter(val items: MutableList<Items>) : RecyclerView.Adapter<ViewHolder>() {

    companion object {
        private const val VIEW_TYPE_TITLE = 0
        private const val VIEW_TYPE_CONTENT = 1
    }

    interface ItemClick {
        fun onItemClick(view: View, position: Int)
    }

    var itemClick: ItemClick? = null
    
    /**
    getItemViewType의 리턴값 Int가 viewType으로 넘어온다.
    viewType으로 넘어오는 값에 따라 viewHolder를 알맞게 처리해주면 된다.
     */
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
        val inflater = LayoutInflater.from(parent.context)
        return when (viewType) {
            VIEW_TYPE_TITLE -> {
                val binding = ItemTitleBinding.inflate(inflater, parent, false)
                TitleViewHolder(binding)
            }

            else -> {
                val binding = ItemContentBinding.inflate(inflater, parent, false)
                ContentViewHolder(binding)
            }
        }
    }

    // 데이터의 index
    override fun getItemId(position: Int): Long {
        return position.toLong()
    }

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

    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        when (val item = items[position]) {
            is Items.TitleItem -> {
                (holder as TitleViewHolder).title.text = item.title
            }

            is Items.ContentItem -> {
                (holder as ContentViewHolder).content.text = item.content

                holder.itemView.setOnClickListener {
                    itemClick?.onItemClick(it, position)
                }
            }
        }
    }

    override fun getItemViewType(position: Int): Int {
        return when (items[position]) {
            is Items.TitleItem -> VIEW_TYPE_TITLE
            is Items.ContentItem -> VIEW_TYPE_CONTENT
        }
    }

    inner class TitleViewHolder(binding: ItemTitleBinding) :
        RecyclerView.ViewHolder(binding.root) {
        val title = binding.tvTitle
    }

    inner class ContentViewHolder(binding: ItemContentBinding) :
        RecyclerView.ViewHolder(binding.root) {
        val content = binding.tvContent
    }
}

MainActivity


class MainActivity : AppCompatActivity() {
    private val binding: ActivityMainBinding by lazy {
        ActivityMainBinding.inflate(layoutInflater)
    }
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(binding.root)

        initView()
    }

    private fun initView() {
        val dataList = mutableListOf( // 샘플 리스트 생성 
            Items.TitleItem("나의 쇼핑정보"),
            Items.ContentItem("주문배송조회"),
            Items.ContentItem("숙박예약조회"),
            Items.ContentItem("취소/교환/반품 내역"),
            Items.ContentItem("상품 리뷰"),
            Items.TitleItem("나의 계정정보"),
            Items.ContentItem("회원 정보 수정"),
            Items.ContentItem("나의 체형 정보"),
            Items.ContentItem("회원등급"),
            Items.ContentItem("쿠폰"),
            Items.ContentItem("마일리지"),
            Items.ContentItem("설정"),
            Items.TitleItem("영상/뉴스"),
            Items.ContentItem("1:1 문의내역"),
            Items.ContentItem("상품 Q&A내역"),
            Items.ContentItem("FAQ"),
            Items.ContentItem("고객의 소리"),
            Items.TitleItem("ABOUT 29CM"),
            Items.ContentItem("공지사항"),
            Items.ContentItem("인재채용"),
            Items.ContentItem("29CM 소개"),
        )

        val adapter = ItemAdapter(dataList) // 어댑터 생성
        binding.recyclerView.adapter = adapter // 어댑터 연결 
        binding.recyclerView.layoutManager = LinearLayoutManager(this)

        adapter.itemClick = object : ItemAdapter.ItemClick {
            override fun onItemClick(view: View, position: Int) {
            	// TODO 아이템 클릭했을 때 액션 
            }
        }
    }
}

Item layout

item_title.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="wrap_content">

    <TextView
        android:id="@+id/tv_title"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="32dp"
        android:padding="@dimen/list_item_padding"
        android:textSize="18sp"
        android:textStyle="bold"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <View
        android:layout_width="match_parent"
        android:layout_height="3dp"
        android:background="@color/black"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@id/tv_title" />

</androidx.constraintlayout.widget.ConstraintLayout>

item_content.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="wrap_content">

    <TextView
        android:id="@+id/tv_content"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:padding="@dimen/list_item_padding2"
        app:layout_constraintEnd_toEndOf="parent"
        android:textSize="16sp"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/view" />

    <View
        android:id="@+id/view"
        android:layout_width="match_parent"
        android:layout_height="1dp"
        android:background="#A9AAAAAA"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />


</androidx.constraintlayout.widget.ConstraintLayout>

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"
    android:padding="16dp"
    tools:context=".MainActivity">

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/recyclerView"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

멀티 뷰 타입 RecyclerView(리사이클러뷰) 만들어보기 (feat. Kotlin)

profile
개발 공부 기록 🌱

0개의 댓글