기록

Androidstudio/kotlin/멀티뷰타입 리사이클러뷰 응용 본문

Moblie/Android

Androidstudio/kotlin/멀티뷰타입 리사이클러뷰 응용

youngyin 2022. 8. 10. 10:00

서론

초기에, 프로젝트에서 위와 같은 화면이 필요했었다. 당시에는 리사이클러뷰 안에 리사이클러뷰를 넣는 방법으로 문제를 해결했었다. 최근에 리사이클러뷰에 여러개의 뷰타입을 연결할 수 있다는 것을 알게 되었다.

2022.05.29 - [안드로이드/예제] - AndroidStudio/java/ 멀티뷰 타입 Recyclerview

View Binding멀티뷰 타입 리사이클러뷰를 활용하여 adapter을 하나만 쓰도록 리팩토링 작업을 했다. 나머지는 동일하고, adapter와 adapter에서 사용하는 dataset의 구조를 변경하였다.

CODE

  • New Adapter
import android.content.Context
import android.content.Intent
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.garosero.android.hobbyroadmap.R
import com.garosero.android.hobbyroadmap.data.MyClass
import com.garosero.android.hobbyroadmap.databinding.RecyclerMylistChildBinding
import com.garosero.android.hobbyroadmap.databinding.RecyclerMylistTitleBinding

class MylistAdapter : RecyclerView.Adapter<RecyclerView.ViewHolder>() {

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
        if (viewType == ItemViewType.Content.value) {
            val view = RecyclerMylistChildBinding.inflate(LayoutInflater.from(parent.context), parent, false)
            return ContentViewHolder(view)
        }
        else {
            val view = RecyclerMylistTitleBinding.inflate(LayoutInflater.from(parent.context), parent, false)
            return TitleViewHolder(view)
        } // end if
    }

    override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
        (holder as? ContentViewHolder)?.bind(dataset[position] as MyClass)
        (holder as? TitleViewHolder)?.bind(dataset[position] as String)
    }

    override fun getItemCount(): Int = dataset.size

    // ================================ view holder
    inner class ContentViewHolder(val binding: RecyclerMylistChildBinding) : RecyclerView.ViewHolder(binding.root) {
        fun bind(item : MyClass){
            binding.apply {
                tvTitle.text = item.title
                tvPercentage.text = item.percentage
                ivRoadmap.setImageResource(item.imgSrc)

                layout.setOnClickListener { // on click 
                }
            }
        }
    }

    inner class TitleViewHolder(val binding: RecyclerMylistTitleBinding) : RecyclerView.ViewHolder(binding.root){
        fun bind(title : String){
            binding.tvTitle.text = title
        }
    }

    // ================================ data type
    override fun getItemViewType(position: Int): Int {
        if (dataset[position] is MyClass) return ItemViewType.Content.value
        return ItemViewType.Title.value
    }

    private enum class ItemViewType(val value : Int){
        Title(100), Content(110)
    }

    // ================================ data control
    var dataset = ArrayList<Any>()

    fun submitData(clazz: ArrayList<MyClass>){
        (clazz as? ArrayList<Any>)?.let { scanData(it) }
        notifyDataSetChanged()
    }

    fun scanData(dataset : ArrayList<Any>){
        val newDataSet = ArrayList<Any>()
        var title : String? = null

        for (item : Any in dataset){
            if (item !is MyClass){
                continue
            } // end if

            if (item.title != title) {
                newDataSet.add(item.title) // add title
                title = item.title

            } // end if

            newDataSet.add(item) // add content
        } // end for

        this.dataset = newDataSet
    }
}
  • Adapter의 dataset 구조 변경

기존에 사용하던 dataset의 타입을 MutableMap<String, ArrayList<MyClass>>에서 ArrayList<Any>으로 변경하였다.

// 기존
{
    title1 : [MyClass1, MyClass2, ...],
    ...
    title2 : [MyClass1, MyClass2, ...]
}

// 변경
[MyClass1, MyClass2, ...]

개선할 점

위 예제는 아이템의 삭제, 추가 로직이 구현되어 있으므로 추가로 로직을 구현한다면, 제목에 대한 예외처리가 필요하다. (예를 들어 title 1개, content 1개가 있을 때, content를 삭제한다면 title이 보여서는 안된다.) 지금 생각해보니 adapter의 데이터 구조를 변경하지 않더라도 dataset을 활용할 수 있다.

Comments