(Android/안드로이드) 이종 데이터를 ListAdapter로 보여주는 방법

1. 모델 정의

data class People(
    val id: Int,
    val name: String
)

sealed class RowItem {
    data class PeopleRow(val people: People) : RowItem()
    data class ImageRow(val url: String) : RowItem()
}

이종의 데이터를 처리할 때 sealed class 활용


2. “position=1에 이미지 삽입” 규칙으로 리스트 빌드 함수

fun initView() {
    val url = "https://www.koreadaily.com/data/photo/2025/12/26/c87c5ca7-6bc1-4176-bdbd-5d8cca642984.jpg"
    val people = mutableListOf<People>()
    (1..20).forEach { people.add(People(it, "name [$it]")) }
    val items = buildItems(people, url)

    val adapter = MultiRowAdapter()
    binding.recyclerView.adapter = adapter
    adapter.submitList(items)
}

3. ListAdapter (멀티 ViewHolder)

import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import com.bumptech.glide.Glide
import com.example.myapplication.databinding.ItemImageBinding
import com.example.myapplication.databinding.ItemPeopleBinding

class MultiRowAdapter(
    private val onPeopleClick: (People) -> Unit = {}
) : ListAdapter<RowItem, RecyclerView.ViewHolder>(Diff) {

    companion object {
        private const val VT_PEOPLE = 1
        private const val VT_IMAGE = 2
    }

    private object Diff : DiffUtil.ItemCallback<RowItem>() {
        override fun areItemsTheSame(oldItem: RowItem, newItem: RowItem): Boolean {
            return when {
                oldItem is RowItem.PeopleRow && newItem is RowItem.PeopleRow ->
                    oldItem.people.id == newItem.people.id

                oldItem is RowItem.ImageRow && newItem is RowItem.ImageRow ->
                    oldItem.url == newItem.url // url이 바뀌면 다른 아이템

                else -> false
            }
        }

        override fun areContentsTheSame(oldItem: RowItem, newItem: RowItem): Boolean {
            return oldItem == newItem
        }
    }

    override fun getItemViewType(position: Int): Int {
        return when (getItem(position)) {
            is RowItem.PeopleRow -> VT_PEOPLE
            is RowItem.ImageRow -> VT_IMAGE
        }
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
        val inflater = LayoutInflater.from(parent.context)
        return when (viewType) {
            VT_PEOPLE -> PeopleVH(
                ItemPeopleBinding.inflate(inflater, parent, false),
                onPeopleClick
            )

            VT_IMAGE -> ImageVH(
                ItemImageBinding.inflate(inflater, parent, false)
            )

            else -> error("Unknown viewType=$viewType")
        }
    }

    override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
        when (val item = getItem(position)) {
            is RowItem.PeopleRow -> (holder as PeopleVH).bind(item.people)
            is RowItem.ImageRow -> (holder as ImageVH).bind(item.url)
        }
    }

    class PeopleVH(
        private val binding: ItemPeopleBinding,
        private val onClick: (People) -> Unit
    ) : RecyclerView.ViewHolder(binding.root) {

        fun bind(item: People) = with(binding) {
            tvName.text = item.name
            root.setOnClickListener { onClick(item) }
        }
    }

    class ImageVH(
        private val binding: ItemImageBinding
    ) : RecyclerView.ViewHolder(binding.root) {

        fun bind(url: String) {
            Glide.with(binding.root.context)
                .load(url)
                .into(binding.ivBanner)
        }
    }

}

4. Layout

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

    <TextView
        android:id="@+id/tvName"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:textSize="16sp"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintTop_toTopOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>

image_people.xml

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:padding="16dp">

    <ImageView
        android:id="@+id/ivBanner"
        android:layout_width="match_parent"
        android:layout_height="160dp"
        android:scaleType="centerCrop" />
</FrameLayout>


5. “ViewHolder가 여러 개일 것도 가정” 확장 포인트

예: RowItem.LoadingRow, RowItem.HeaderRow, RowItem.ErrorRow 등


6. glide 의존성 추가

implementation("io.coil-kt:coil:2.6.0")


Related Posts