본문 바로가기

개발 노트/Kotlin

[Android/Kotlin] Multi View Type 리사이클러뷰 구현하기

일반적인 리사이클러뷰는 하나의 뷰형태만 보여주고 데이터만 달라지지만,

리사이클러뷰 멀티뷰타입을 사용하면 다수의(다른) 뷰형태를 가지는 아이템을 보여줄 수 있다

 

이런식으로 멀티뷰타입을 통해서 구현하면 아이템들을 다르게 보여줄 수 있다

 

 

 

 

 

# xml

먼저 해당 아이템에 들어갈 레이아웃을 각각 디자인해준다

recyclerview item

 

 

 

# 상수정의

enum class를 사용해서 방금만든 뷰타입의 상수를 정의해준다

// enum class로 열거형 클래스로 만들기 (코드 단순, 가독성 up)
enum class MultiViewEnum(val viewType : Int) {

    BlUE(0),
    LIGHTBLUE(1),
    ORANGE(2)
}

 

 

 

# 데이터

들어갈 데이터클래스를 작성해준다

@Parcelize
data class CardData(
    val id : Int,
    val name : String,       //이름
    val cardName : String,   //카드이름
    val number : String,     //카드번호
    val date : String,       //유효기간
    val price : Double,      //가격
    val type : MultiViewEnum   // 멀티뷰타입 -> enum class로 만든 클래스 선언
    
): Parcelable

CardData.kt

 

 

싱글톤을 사용해서 데이터소스를 어디서나 편리하게 호출할 수 있도록 만들어준다

// 싱글톤
class DataSource {
    companion object{
        private var INSTANCE : DataSource? = null

        fun getDataSoures() : DataSource{
            // DatoSource::class 객체에 lock을 걸어 한번에 한 스레드에서만 실행 되도록 함
            return synchronized(DataSource::class) {
                // 싱글톤 객체를 한번 호출하고 없으면 DataSource반환, 있으면 생성된 인스턴스 반환
                val newInstance = INSTANCE ?: DataSource()
                INSTANCE = newInstance
                newInstance
            }
        }
    }

    // MVVM패턴에서 Model에 해당한다고 볼 수 있음
    fun getCardList() : List<CardData>{
        // 만들어놓은 데이터클래스 리턴
        return CardDataList()
    }


}

DataSource.kt

 

 

데이터리스트를 만들어서 실제 들어갈 데이터를 넣어준다

fun CardDataList() : ArrayList<CardData>{

    return arrayListOf(
        CardData(
            id = 1,
            name = "Juwon",
            cardName = "A Debit Card",
            number = "2423 3581 9503",
            date = "21/27",
            price = 3100.30,
            MultiViewEnum.BlUE
        ),
        CardData(
            id = 2,
            name = "Minju",
            cardName = "A Hybrid Card",
            number = "5423 3581 9503",
            date = "07/25",
            price = 4100.30,
            MultiViewEnum.LIGHTBLUE
        ),
        CardData(
            id = 3,
            name = "Hemin",
            cardName = "A Hi Card",
            number = "9423 3581 9503",
            date = "23/29",
            price = 4170.30,
            MultiViewEnum.ORANGE
        ),
    )

}

CardDataList.kt

 

 

 

 

 

 

# Adapter

리사이클러뷰 멀티뷰타입 어댑터의 전체코드는 아래와같다

class CardViewAdpater(var cardList : List<CardData>, private val onClick : (CardData) -> Unit) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {


    // 레이아웃 연결
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
        val inflater = LayoutInflater.from(parent.context)

        return when(viewType){

            MultiViewEnum.BlUE.viewType -> {
                val binding = RecyclerviewItem1Binding.inflate(inflater, parent, false)
                BlueTypeViewHolder(binding)
            }
            MultiViewEnum.LIGHTBLUE.viewType -> {
                val binding = RecyclerviewItem2Binding.inflate(inflater, parent, false)
                LightBlueTypeViewHolder(binding)
            }
            MultiViewEnum.ORANGE.viewType -> {
                val binding = RecyclerviewItem3Binding.inflate(inflater, parent, false)
                OrangeTypeViewHolder(binding)
            }
            else -> throw IllegalAccessException("Invalid view type")
        }
    }

    // 아이템 개수 리턴
    override fun getItemCount(): Int {
        return cardList.size
    }


    // 멀티뷰타입은 getItemViewType을 오버라이딩 해줘야함
    // postion에 따라 어떤 뷰타입을 가져야되는지 연결해줘야함
    override fun getItemViewType(position: Int): Int {
        return when(position){
            0 -> MultiViewEnum.BlUE.viewType
            1 -> MultiViewEnum.LIGHTBLUE.viewType
            2 -> MultiViewEnum.ORANGE.viewType
            else -> throw IllegalAccessException("Invalid view type")
        }
    }


    // 데이터 연결
    override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
        val cardlist = cardList[position]

        when(holder.itemViewType){

            MultiViewEnum.BlUE.viewType -> {
                val blueHolder = holder as BlueTypeViewHolder
                blueHolder.bind1(cardlist)

                // MULTI_VIEWTYPE1 클릭 이벤트 처리 (멀티뷰타입마다 데이터가 완전 다른 경우도 있기 때뮨에 그런경우에는 이렇게 클릭이벤트를 주는게 유용함)
                holder.itemView.setOnClickListener {
                    onClick(cardlist)
                }
            }

            MultiViewEnum.LIGHTBLUE.viewType -> {
                val lightBlueHolder = holder as LightBlueTypeViewHolder
                lightBlueHolder.bind2(cardlist)

                // MULTI_VIEWTYPE2 클릭 이벤트 처리
                holder.itemView.setOnClickListener {
                    onClick(cardlist)
                }
            }

            MultiViewEnum.ORANGE.viewType -> {
                val orangeHolder = holder as OrangeTypeViewHolder
                orangeHolder.bind3(cardlist)

                // MULTI_VIEWTYPE3 클릭 이벤트 처리
                holder.itemView.setOnClickListener {
                    onClick(cardlist)
                }
            }

        }


    }


    class BlueTypeViewHolder(private val binding : RecyclerviewItem1Binding) : RecyclerView.ViewHolder(binding.root){

        fun bind1(card : CardData){
            with(binding){
                AndersonTv.text = card.name
                debitCardTv.text = card.cardName
                cardNumberTv.text = card.number
                cardDateTv.text = card.date
                cardPriceTv.text = "$" + DecimalFormat("#,##0.00").format(card.price).toString()
            }
        }
    }


    class LightBlueTypeViewHolder(private val binding : RecyclerviewItem2Binding) : RecyclerView.ViewHolder(binding.root){

        fun bind2(card : CardData){
            with(binding){
                AndersonTv2.text = card.name
                debitCardTv2.text = card.cardName
                cardNumberTv2.text = card.number
                cardDateTv2.text = card.date
                cardPriceTv2.text = "$" + DecimalFormat("#,##0.00").format(card.price).toString()
            }
        }
    }


    class OrangeTypeViewHolder(private val binding : RecyclerviewItem3Binding) : RecyclerView.ViewHolder(binding.root){

        fun bind3(card : CardData){
            with(binding){
                AndersonTv3.text = card.name
                debitCardTv3.text = card.cardName
                cardNumberTv3.text = card.number
                cardDateTv3.text = card.date
                cardPriceTv3.text = "$" + DecimalFormat("#,##0.00").format(card.price).toString()
            }
        }
    }

}

 

CardViewAdpater.kt

 

 

 

 

# MainActivity

class MainActivity : AppCompatActivity() {

    private val binding : ActivityMainBinding by lazy {
        ActivityMainBinding.inflate(layoutInflater)
    }

    // onClick메소드가 실행되면 람다식이 바로 실행되도록
    private val cardAdapter : CardViewAdpater by lazy {
        CardViewAdpater(cardList = ArrayList<CardData>()) { card ->
            // DetailActivity로 이동하는 함수 실행
            adapterClick(card)
        }
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        setContentView(binding.root)

        val dataSource = DataSource.getDataSoures().getCardList()
        cardAdapter.cardList = dataSource

        with(binding.recyclerview) {
            adapter = cardAdapter	// 리사이클러뷰와 어뎁터 연결
            layoutManager = LinearLayoutManager(this@MainActivity)
        }
    }

    private fun adapterClick(card : CardData) {
    	// DetailActivity로 이동
        val intent = Intent(this, DetailActivity::class.java)
        val bundle = Bundle().apply {
            putParcelable(DetailActivity.EXTRA_CARD, card)
        }
        intent.putExtras(bundle)
        startActivity(intent)
    }
}

MainActivity.kt

 

 

 

# DetailActivity

class DetailActivity : AppCompatActivity() {

    private val binding: ActivityDetailBinding by lazy {
        ActivityDetailBinding.inflate(layoutInflater)
    }

    // 어디서나 이 키값을 사용할수있게끔 지정!
    companion object{
        const val EXTRA_CARD : String = "extra_card"
    }


    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(binding.root)

          // Intent로 전달한 데이터 받아옴
          val intent = getIntent()
          val cardItem = intent?.getParcelableExtra<CardData>(EXTRA_CARD)
  
          with(binding) {
              detailCardnameTv.text = "이름: ${cardItem?.name}"
              detailCardnunberTv.text = "카드번호: ${cardItem?.number}"
              detailDateTv.text = "유효기간: ${cardItem?.date}"
              detailPriceTv.text = "가격: $${DecimalFormat("#,##0.00").format(cardItem?.price)}"
          }

    }
}

DetailActivtiy.kt

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 


# 참고자료

https://yunaaaas.tistory.com/61

 

[Android/Kotlin] 멀티뷰 타입 RecyclerView 구현하기

오늘은 다음과 같은 멀티뷰 타입의 리사이클러뷰를 구현해보도록 하겠습니다 ! 사이드 프로젝트를 하면서 리사이클러뷰의 여러 itemview를 적용할 수 있는 방법에 대해 찾아보던 중 멀티뷰 타입

yunaaaas.tistory.com

https://velog.io/@ouowinnie/AndroidKotlin-08-%EB%A6%AC%EC%82%AC%EC%9D%B4%ED%81%B4%EB%9F%AC%EB%B7%B0-%EB%A9%80%ED%8B%B0%EB%B7%B0-%ED%83%80%EC%9E%85Feat.-%EB%91%90%EA%B0%9C%EC%9D%98-%EC%95%84%EC%9D%B4%ED%85%9C-%ED%95%98%EB%82%98%EC%9D%98-%EB%A6%AC%EC%8A%A4%ED%8A%B8

 

[Android/Kotlin 08] 리사이클러뷰 멀티뷰 타입(Feat. 두개의 아이템 하나의 리스트)

리사이클러뷰 멀티 뷰 타입이란 여러 개의 뷰 타입, 즉 리사이클러뷰 내에서 한 개의 뷰 형태만을 랜더링 하는 게 아니라 다수의 뷰 형태를 가지는 객체들을 랜더링하는 방법입니다. 리사이클러

velog.io

https://velog.io/@cksgodl/RecyclerView-%EA%B0%81%EA%B0%81-%EC%95%84%EC%9D%B4%ED%85%9C%EB%A7%88%EB%8B%A4-%ED%81%B4%EB%A6%AD-%EC%9D%B4%EB%B2%A4%ED%8A%B8-%EC%84%A4%EC%A0%95

 

[Android/Kotlin] RecyclerView 각각 아이템마다 클릭 이벤트 설정

GitHub Link네이버 API로 가져온 뉴스들이다.뉴스를 화면에 뿌려주는 것까지는 좋았는데 뉴스를 클릭할 시에 각각 뉴스의 링크로 이동하고 싶은데 어떻게 해야할까?어뎁터 함수내에서 외부와 연결

velog.io