


기초적으로 RecyclerView를 사용하면, 아래와 같이 Adapter클래스를 생성하고, RecyclerView클래스의 Adapter를 상속받아 구현한다.
class UserRecyclerViewAdapter : RecyclerView.Adapter<UserRecyclerViewAdapter.MyViewHolder>() {
private val userList = ArrayList<User>()
class MyViewHolder(private val binding:ItemListBinding) : RecyclerView.ViewHolder(binding.root){
fun bind(user:User){
binding.apply {
binding.user = user
}
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
return MyViewHolder(
ItemListBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
)
)
}
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
holder.apply { bind(userList[position]) }
}
override fun getItemCount(): Int = userList.size
fun setItem(items: ArrayList<User>) {
userList.clear()
userList.addAll(items)
notifyDataSetChanged()
}
}
이를 fragment등에서 사용하는 방법은 아래와 같다.
val adapter = UserRecyclerViewAdapter()
// 구현한 Adapter를 등록한다.
binding.recyclerView.adapter = adapter
// Item으로 사용될 List(userList)를 넘겨준다.
adapter.setItem(userList)
// RecyclerView 갱신을 위해 사용
adapter.notifyDataSetChanged()
이렇게 사용할 경우, Recyclerview의 데이터가 변하면 notifyDataSetChanged 메소드를 사용해서 ViewHolder 내용을 갱신해줘야 한다.
데이터가 변경될 때 마다 notifyDataSetChanged를 계속 호출 해줘야 하기에 번거롭기도 하고, 또 갱신이 필요없는 항목들까지 전부 갱신하여 메모리 리소스를 많이 사용한다는 단점이 있다. 또한 갱신 시 화면이 깜빡이는 이슈도 있다.
이는 DiffUtil을 사용하여 해결할 수 있다.
https://developer.android.com/reference/androidx/recyclerview/widget/DiffUtil
DiffUtil은 두 Data Set을 받아서 그 차이를 계산해주는 클래스이다.
DiffUtil을 사용하면 두 Data Set을 비교한 뒤 그 중 변한 부분만을 파악하여 RecyclerView에 반영할 수 있다.
참고로 DiffUtil은 Eugene W. Myers의 difference 알고리즘을 이용해서 O(N + D^2)시간 안에 리스트의 비교를 수행한다.
(N:추가 및 제거된 항목의 갯수, D: 스크립트의 길이)
DiffUtil은 아래와 같은 절차를 거친다.
- DiffUtil.ItemCallback()을 상속받아 areItemsTheSame으로 비교대상인 두 객체가 동일한지 확인
- areContentsTheSame으로 두 아이템이 동일한 데이터를 가지는지 확인
DiffUtil.ItemCallback 구현
object TermsListDiffCallback : DiffUtil.ItemCallback<User>() {
override fun areItemsTheSame(oldItem: User, newItem: User): Boolean {
return oldItem == newItem
}
override fun areContentsTheSame(oldItem: User, newItem: User): Boolean {
return oldItem.id == newItem.id
}
}
이 DiffUtil은 위에서 언급한 바와 같이 O(N + D^2)의 시간 복잡도를 가진다고 했으므로, List의 양이 많아지면 그만큼 비교해야 할 대상이 많아지므로, Main Thread에 작성하기엔 어려움이 있다.
Recyclerview 어댑터를 만들때 ListAdapter를 상속하도록 하고, 이 DiffUtil.Callback객체를 받도록 하면, 백그라운드 Thread에서 동작하는 효율적인 RecyclerView를 구현할 수 있다.
위의 UserRecyclerViewAdapter.kt를 리팩토링한 소스이다.
package com.example.recyclerview2.adapter
import android.util.Log
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import com.example.recyclerview2.data.User
import com.example.recyclerview2.databinding.ItemListBinding
class UserRecyclerViewAdapter :
ListAdapter<User, UserRecyclerViewAdapter.MyViewHolder>(TermsListDiffCallback) {
class MyViewHolder(private val binding: ItemListBinding) :
RecyclerView.ViewHolder(binding.root) {
fun bind(user: User) {
binding.user = user
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
return MyViewHolder(
ItemListBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
)
)
}
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
holder.apply {
bind(getItem(position))
}
}
object TermsListDiffCallback : DiffUtil.ItemCallback<User>() {
override fun areItemsTheSame(oldItem: User, newItem: User): Boolean {
return oldItem == newItem
}
override fun areContentsTheSame(oldItem: User, newItem: User): Boolean {
return oldItem.id == newItem.id
}
}
}
우선 달라진 점은
1. UserRecyclerViewAdapter클래스가 RecyclerView.Adapter를 상속받는 대신, ListAdapter를 상속받는다.
2. DiffUtil을 구현한 TermsListDiffCallback객체를 매개변수로 전달해준다.
3. Item의 size를 반환하는 getItemCount() 메서드와 Items를 Set해주는 setItem(items: ArrayList) 메서드가 사라졌다.
이를 아래와 같이 사용한다.
val adapter = UserRecyclerViewAdapter()
binding.recyclerView. = adapter
adapter.submitList(userList)
기존에 별도로 구현했던 setItems메서드 대신 ListAdapter클래스의 submitList메서드를 사용한 것을 볼 수 있다.
이렇게 RecyclerView를 DiffUtil과 ListAdapter를 활용하여 보다 효율적으로 구현할 수 있다.
BindingAdapter를 사용하여 RecyclerViewAdapter를 아래와 같이 연결하는 도중, 화면 갱신이 되지 않는 이슈를 만났다. 이슈를 분석해 본 결과, 원인은 submitList 메서드에 있었다.
submitList(gitUserList)
submitList메서드를 타고 들어가다보면, AsyncListDiffer.java 클래스에서 아래와 같은 로직을 볼 수 있다.

newList와 기존List를 비교하여, 같으면 return 하는 로직이다.
애초에 submitList로 전달하는 객체(여기서는 gitUserList:ArrayList)를 계속 같은 객체에다가 값만 변경하여 넣어주었더니, 같은 주소를 참조하여 저 로직에서 같다고 판단한 것.
따라서 해결 방법은
1. 애초에 submitList를 할 때 새로운 객체를 넣어주던가
2. submitList(gitUserList.toMutableList())와 같이 새로운 객체에 담아 넣어주면 된다.