본문 바로가기

안드로이드

리사이클러뷰 데이터가 꼬일때 해결하는 방법

리사이클러뷰기능을 구현할 때는 항상 적어도20~30개 이상의 데이터를 넣어보고 테스트 하는게 좋다고 한다.
그렇게 해야지 뷰에 문제가 생기는지 아닌지 확인할 수 있기 때문이다.
재활용을 제대로 하지 못하면 이전에 사용했던 뷰홀더가 다시 나타나서 원치 않은 대로 기능이 동작할 수 있다.
 
위의 동영상과 같이 말이다.
 
이 문제가 발생하는 원인은 리사이클러뷰의 특징상 사용했던 뷰홀더를 재사용 하기 때문이다.
리사이클러뷰의 아이템을 롱클릭 하면 색상이 변하게끔 기능을 구현했다.
하지만 스크롤을 위아래로 내리고 바꾸다 보면 롱클릭으로 색상을 바꾸지 않은 녀석들도 바뀌는 것을 볼 수 있다.
 
리서치를 해봤는데 문제를 해결하는 다양한 방법이 있었다.
 
방법1 : getItemViewType(int position) 메소드 재정의하기
아래는 공식 API의 내용을 의역한 것이다.
 
Return the view type of the item at position for the purposes of view recycling.
포지션에 해당되는 아이템의 뷰타입을 리턴해준다. 뷰가 재활용 되기위한 목적을 위해서.
 
The default implementation of this method returns 0, making the assumption of a single view type for the adapter. Unlike ListView adapters, types need not be contiguous. Consider using id resources to uniquely identify item view types.
기본적으로 이 메소드의 구현은 0을 반환하는데 그 이유는 어댑터의 단일 추정값을 만들기 위해서다.
(뭔말이지)
리스트뷰의 어댑터와는 다르게,  타입이 근접할 필요가 없다. 아이템뷰의 타입을 고유하게 인식하기위한 id 리소스를 사용하는 것을 고려해봐라.
가장 위에서 언급한 문장이 핵심이다. 뷰가 재활용되기 위해서 아래와 같이 메소드를 오버라이딩해주면 된다.
override fun getItemViewType(position: Int): Int {
    return position
}
방법2 : 조건에 따라서 뷰홀더를 다르게 설정해주기
아래의 코드는 리사이클러뷰의 포지션 값을 기준으로 색상 값이 흰색이 되게 할지 노랑색이 되게 할지 판별해주는 조건문이 있는 코드이다. 그리고 SparseBooleanArray() 를 활용해서 이 기능을 구현하고 있다.
하지만 이는 좋은 방법이 아니다. 만약 리사이클러뷰에 새로운 아이템이 추가되거나 삭제가 되는 경우 포지션 값이 꼬이게 될 확률이 높다.
(단, 나는 뷰가 서로 다르게 보이게 되는 조건을 poisiton으로 둬서 그렇지 구글링을 해서 조금 살펴보면 다른 분들은 다른 기준으로 둬서 해결하신 분들도 계신다. 그런 경우는 또 상관 없을듯.)
눈여겨봐야 할 코드는 private val longClickedItem = SparseBooleanArray() 과 bind 메소드 안에서 나눠지는 조건문이다.
class PlantListAdapter(private val onItemClickListener: OnItemClickListener) :
    RecyclerView.Adapter<PlantListAdapter.ViewHolder>() {

    private val longClickedItem = SparseBooleanArray()
    private val plantData = mutableListOf<Plant>()

    interface OnItemClickListener {
        fun onItemClick(plant: Plant)
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
        LayoutInflater.from(parent.context)
        val itemView =
            LayoutInflater.from(parent.context).inflate(R.layout.plant_item_list, parent, false)
        return ViewHolder(itemView)
    }

    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        holder.bind(plantData[position])

    }

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

    inner class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
        val plantName: TextView = itemView.findViewById(R.id.plantName)
        val plantImage: ImageView = itemView.findViewById<ImageView>(R.id.palntImage)

        fun bind(item: Plant) {
            plantName.text = item.location
            Glide.with(itemView.context).load(item.imageResource).into(plantImage)
            // 이미지뷰의 모서리를 약간 둥글게 만든다.
            plantImage.clipToOutline = true

            if (longClickedItem.get(adapterPosition) == true) {
                itemView.setBackgroundColor(
                    ContextCompat.getColor(
                        itemView.context,
                        R.color.yellow
                    )
                )
            } else {
                itemView.setBackgroundColor(ContextCompat.getColor(itemView.context, R.color.white))
            }

        }

        init {
            itemView.setOnClickListener {
                onItemClickListener.onItemClick(plantData.get(adapterPosition))
            }



            itemView.setOnLongClickListener {
                itemView.setBackgroundColor(
                    ContextCompat.getColor(
                        itemView.context,
                        R.color.yellow
                    )
                )
                longClickedItem.put(adapterPosition, true)
                true
            }
        }

    }

    fun setData(data: List<Plant>) {
        this.plantData.clear()
        this.plantData.addAll(data)
        // 아이템이 총 몇개에서 몇개로 변하는지 정확하게 알 수 없기 때문에 이 메소드를 사용
        notifyDataSetChanged()
    }

}

 

방법3 : 재활용 막기 (이건 진짜 아니다.)
holder.setIsRecyclable(false);
이 방법은 문제를 해결하는 방법이라고 말하기는 어려울 것 같다. 리사이클러뷰가 애초에 뷰를 재사용하려고 쓰는 건데 재활용을 막는 다는게 말이 안된다.
군대에서 선임들만 행복 쉼터 노래방을 쓴다고 건의가 나와서 대대장이 행복 쉼터를 없애버렸다는 이야기랑 다를게 없다.
 
 
이 글을 작성하는데 도움이 됐던 블로그들
리사이클러뷰가 동작하는 방식을 정말 구체적으로 잘 설명해주셨다. 진짜 꼭 읽어볼 것을 권장한다.

'안드로이드' 카테고리의 다른 글

viewPager2 Indicator 예제  (0) 2024.02.01
DiffUtil이란?  (0) 2023.11.17
Handler, Looper 핸들러와 루퍼의 의미  (0) 2023.10.16
Handler, Looper, Message  (0) 2023.10.13
안드로이드 Repository, Data Layer 디자인 패턴  (2) 2023.10.10