ListView와 RecyclerView에서 스크롤 시 checkbox, switch 설정 해제되는 현상 원인과 해결

JIYOON·2021년 7월 26일
1

Android

목록 보기
9/9
post-thumbnail

📣 목표
안드로이드의 ListView나 RecyclerView에서 checkbox, switch 등을 사용할 때 스크롤을 하면 체크한 체크박스가 해제되거나 다른 체크박스에 체크돼 있는 현상을 볼 수 있다. 이러한 아이템 상태 변화 현상을 해결해보자!


1️⃣ 문제

RecyclerView에 체크박스를 활용한 예제이다.
위와 같이 체크박스에 체크하고 스크롤했을 시 체크하지 않은 체크박스에 체크가 돼있는 것을 확인할 수 있다.


2️⃣ 원인

이는 ListView와 RecyclerView에서 스크롤하면 아이템 뷰를 재사용하기 때문에 발생하는 문제이다.


3️⃣ 해결

다음의 두 가지 방법으로 해결하면 위와 같이 아이템 상태 변화 값을 저장할 수 있다.
방법은 다르나 둘 다 아이템의 위치(position)과 선택 여부(boolean) 값을 저장해서 이를 각 아이템에 연결한다는 원리는 같다.

  1. sparseBooleanArray를 활용한다.
  2. position과 상태를 저장할 변수를 가진 데이터 클래스를 이용한다.

01. sparseBooleanArray 활용

private val checkboxStatus = SparseBooleanArray()

위 코드를 어댑터에 추가한다.

SparseBooleanArray는 정수 값들을 boolean 값들로 매핑시키는 기능을 하는 클래스이다. 정수 값은 RecyclerView 아이템의 위치(position)로 사용되고, boolean 값은 선택(Checked) 여부로 사용된다.
SparseBooleanArray의 get() 함수를 이용해서 인자로 입력되는 position의 상태가 Checked인지 확인할 수 있다.

checkboxUser.isChecked = checkboxStatus[adapterPosition]

checkboxUser.setOnClickListener {
	if (!checkboxUser.isChecked)
		checkboxStatus.put(adapterPosition, false)
	else
		checkboxStatus.put(adapterPosition, true)
notifyItemChanged(adapterPosition)
}

아이템 뷰홀더 클래스에 위 코드를 추가한다.
Array에 아이템의 position과 boolean 값을 넣어서 체크된 체크박스의 위치와 값을 저장한다.



02. data class 활용

1) RecyclerView

data class UserCheckStatus(val position: Int, var isChecked: Boolean)

체크박스의 position과 boolean 값을 저장할 데이터 클래스를 만든다.

private val userCheckBoxStatus = arrayListOf<UserCheckStatus>()

어댑터에 위 코드를 추가하여 데이터 클래스의 array를 정의한다.

override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
	if (holder is UserItemViewHolder)
		holder.bind(userItems[position], userCheckBoxStatus[position])
}

BindViewHolder 함수에 userCheckBoxStatus[position] 코드를 추가하여 체크 박스의 포지션을 바인드한다.

fun addUserItems(user: User){
	userItems.add(user)
	userCheckBoxStatus.add(UserCheckStatus(userItems.size - 1, false))
	notifyItemInserted(userItems.size-1)
}

아이템 목록을 생성하는 함수가 있을 시에는 userCheckBoxStatus.add(UserCheckStatus(userItems.size - 1, false))와 같이 아이템이 생성될 때 체크박스의 상태를 정의하는 코드를 추가해준다.

inner class UserItemViewHolder(private val binding: ItemUserBinding) : RecyclerView.ViewHolder(binding.root){
	fun bind(userItem: User) = with(binding){
            tvUserId.text = userItem.userId
            tvUserName.text = userItem.userName

            checkboxUser.setOnClickListener {
                Log.e("CheckBox", "$adapterPosition ${checkboxUser.isChecked}")
            }
        }
    }

위는 기존 코드, 아래는 변경된 후의 코드이다.
bind함수에 userStatus: UserCheckStatus를 파라미터로 넘겨준 후 checkboxUser.isChecked = userStatus.isChecked 코드로 xml의 checkboxUser 체크박스 뷰 아이디와 UserCheckStatus 데이터 클래스의 값을 바인드한다. 그 후 체크박스의 setOnClickListener에서 adapterpostion에 체크 여부를 저장하는 코드를 입력한다.

inner class UserItemViewHolder(private val binding: ItemUserBinding) : RecyclerView.ViewHolder(binding.root){
        fun bind(userItem: User, userStatus: UserCheckStatus) = with(binding){
            tvUserId.text = userItem.userId
            tvUserName.text = userItem.userName

            checkboxUser.isChecked = userStatus.isChecked

            checkboxUser.setOnClickListener {
                userStatus.isChecked = checkboxUser.isChecked
                notifyItemChanged(adapterPosition)
            }
        }
    }

2) ListView

data class BusinessCard(val name: String, val contents: String, var isSwitched: Boolean = false)

리스트뷰에 스위치 버튼을 사용한 예제이다.
스위치가 선택됐는지 Boolean 값을 저장하는 데이터 클래스를 생성한다.

binding.mainSwitch.setChecked(businessCardArrayList[position].isSwitched)
binding.mainSwitch.setOnCheckedChangeListener { CompoundButton, onSwitch -> businessCardArrayList[position].isSwitched = onSwitch }

데이터클래스의 array를 만들어 array의 아이템 포지션의 boolean값을 스위치의 setChecked 함수로 넘긴다.
array의 포지션, boolean 값을 스위치의 setOnCheckedChangeListener로 넘긴다.


✅ Reference

[Android] RecyclerView 스크롤시 설정 해제되는 현상(체크박스, 백그라운드색상 등)해결법 by 리안94

0개의 댓글