일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |
- video caching
- ListAdapter DiffUtil
- 스피너
- android custom view
- Zsh
- ktor api call
- list map
- ktor client
- getChangePayload
- kotlin collection
- android exoplayer
- ListAdapter
- map
- 안드로이드
- FastAPI
- Python
- kotlin list
- 유튜브
- llm
- ExoPlayer
- ChatGPT
- doc2vec
- 독서
- android ktor
- AWS EC2
- android
- 시행착오
- DiffUtil.ItemCallback
- build with ai
- exoplayer cache
- Today
- Total
버튼 수집상
[안드로이드] ListAdapter DiffUtil 제대로 쓰기 - 2 본문
1편에서 ListAdapter에 submitList를 하면서 기존 리스트를 업데이트할 때
리스트 깊은 복사deep copy를 해야 하는 이유에 대해 적었다.
이번엔 ListAdapter DiffUtil의 각 함수를 자세히 알아보겠다.
샘플 코드 전체는 1편에 있다.
DiffUtil.ItemCallback 에 구현해야하는 함수 2가지가 있다.
areContentsTheSame : 리스트 요소의 객체 주소를 비교한다
areItemsTheSame : 리스트 요소의 필드값을 비교한다
함수 이름만 보면 하는 일이 반대가 돼야할 것 같은데 아무튼 그렇다.
areItemsTheSame에서 변경사항을 감지하고 싶은 값을 비교해서 UI를 업데이트할 수 있다.
예제 데이터 SimpleObject의 isChecked 값을 비교해보겠다.
class SimpleAdapter : ListAdapter<SimpleObject, SimpleAdapter.SimpleHolder>(object : DiffUtil.ItemCallback<SimpleObject>() {
override fun areContentsTheSame(oldItem: SimpleObject, newItem: SimpleObject): Boolean {
return oldItem == newItem
}
// isChecked가 변경된 뷰홀더 체크
override fun areItemsTheSame(oldItem: SimpleObject, newItem: SimpleObject): Boolean {
return oldItem.isChecked == newItem.isChecked
}
}) {
override fun getItemViewType(position: Int) = position
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SimpleHolder =
SimpleHolder(LayoutInflater.from(parent.context).inflate(R.layout.item_grid, parent, false))
// submitList 로 업데이트되면 onBindViewHolder 호출
override fun onBindViewHolder(holder: SimpleHolder, position: Int) {
val isChecked = currentList[position].isChecked
val bgColor = if (isChecked) Color.parseColor("#30ff0000") else Color.parseColor("#00ff0000")
holder.itemView.findViewById<ImageView>(R.id.iv).setColorFilter(bgColor)
holder.itemView.findViewById<TextView>(R.id.title).text = currentList[position].name
holder.itemView.setOnClickListener {
// 클릭 이벤트 (api 호출)
EventBus.fire(ClickEvent(position, "isClicked"))
}
}
}
그러면 submitList로 리스트가 업데이트 됐을 때
areItemsTheSame 으로 isChecked 변경여부를 읽고 onBindViewHolder가 호출된다.
그런데 보다시피 문제가 있다.
isChecked가 객체를 대변하는 유니크한 값이 아니다보니 변경할 객체를 제대로 찾아가지 못한다.
그리고 onBindViewHolder로 뷰홀더가 다시 그려질 때 반짝이는 것을 볼 수 있다.
UI를 부분 업데이트할 땐 DiffUtil.ItemCallback의 getChangePayload 함수로 처리하는 것이 훨씬 자연스럽다.
getChangePayload : areItemsTheSame에서 비교하는 필드값은 같은데, 요소 객체가 바뀌었을 때 호출된다. 변경값을 리턴한다.
getChangePayload에서 리턴한 변경값(payload)은
onBindViewHolder(holder, position, payloads) 함수를 구현해서 전달받는다.
class SimpleAdapter : ListAdapter<SimpleObject, SimpleAdapter.SimpleHolder>(object : DiffUtil.ItemCallback<SimpleObject>() {
override fun areContentsTheSame(oldItem: SimpleObject, newItem: SimpleObject): Boolean {
return oldItem == newItem
}
// 객체를 대변하는 유니크한 값 비교
override fun areItemsTheSame(oldItem: SimpleObject, newItem: SimpleObject): Boolean {
return oldItem.name == newItem.name
}
// isChecked가 true인 뷰홀더 체크
override fun getChangePayload(oldItem: SimpleObject, newItem: SimpleObject): Any? {
return if (oldItem.isChecked == newItem.isChecked) {
super.getChangePayload(oldItem, newItem)
} else {
newItem.isChecked
}
}
}) {
override fun getItemViewType(position: Int) = position
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SimpleHolder =
SimpleHolder(LayoutInflater.from(parent.context).inflate(R.layout.item_grid, parent, false))
// getChangePayload 에서 호출되지 않는다
override fun onBindViewHolder(holder: SimpleHolder, position: Int) {
holder.itemView.findViewById<TextView>(R.id.title).text = currentList[position].name
holder.itemView.setOnClickListener {
// 클릭 이벤트 (api 호출)
EventBus.fire(ClickEvent(position, "isClicked"))
}
}
// getChangePayload에서 payload 값을 받아온다
override fun onBindViewHolder(holder: SimpleHolder, position: Int, payloads: MutableList<Any>) {
if (payloads.isEmpty()) {
super.onBindViewHolder(holder, position, payloads)
} else {
(payloads[0] as? Boolean)?.let {
holder.turnOn(it)
} ?: run {
super.onBindViewHolder(holder, position, payloads)
}
}
}
class SimpleHolder(private val view: View) : RecyclerView.ViewHolder(view) {
fun turnOn(isChecked: Boolean) {
val bgColor = if (isChecked) Color.parseColor("#30ff0000") else Color.parseColor("#00ff0000")
view.findViewById<ImageView>(R.id.iv).setColorFilter(bgColor)
}
}
}
getChangePayload의 리턴 타입은 Any?로 정의돼있어서
변경사항을 원하는 형태로 전달할 수 있다.
payload를 이용하면 UI가 반짝거리지 않고 깔끔하게 업데이트 되는 것을 볼 수 있다.
그동안 DiffUtil을 아무 생각없이 구현하고는 했는데,
UI를 부분 업데이트할 일이 더 많은 이상, 앞으로는 getChangePayload를 애용하게 될 것 같다.
'TIL - 안드로이드' 카테고리의 다른 글
[안드로이드] ListAdapter DiffUtil 제대로 쓰기 - 3 (0) | 2023.07.28 |
---|---|
[안드로이드] 중복체크 되는 리스트 Preference 저장하고 테스트하기 (0) | 2023.07.20 |
[안드로이드] ListAdapter DiffUtil 제대로 쓰기 - 1 (0) | 2023.07.13 |
[안드로이드] 시인성 향상 애니메이션 제거 여부 알기 (0) | 2023.07.03 |
[안드로이드] RecyclerView에 radius 적용하기 (0) | 2023.07.03 |