📣 목표
안드로이드의 ListView나 RecyclerView에서 checkbox, switch 등을 사용할 때 스크롤을 하면 체크한 체크박스가 해제되거나 다른 체크박스에 체크돼 있는 현상을 볼 수 있다. 이러한 아이템 상태 변화 현상을 해결해보자!
RecyclerView에 체크박스를 활용한 예제이다.
위와 같이 체크박스에 체크하고 스크롤했을 시 체크하지 않은 체크박스에 체크가 돼있는 것을 확인할 수 있다.
이는 ListView와 RecyclerView에서 스크롤하면 아이템 뷰를 재사용하기 때문에 발생하는 문제이다.
다음의 두 가지 방법으로 해결하면 위와 같이 아이템 상태 변화 값을 저장할 수 있다.
방법은 다르나 둘 다 아이템의 위치(position)과 선택 여부(boolean) 값을 저장해서 이를 각 아이템에 연결한다는 원리는 같다.
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 값을 넣어서 체크된 체크박스의 위치와 값을 저장한다.
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)
}
}
}
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
로 넘긴다.
[Android] RecyclerView 스크롤시 설정 해제되는 현상(체크박스, 백그라운드색상 등)해결법 by 리안94