오늘은 ListAdapter와 관련되어 배운 것과 사용법에 대해 작성하겠습니다
기존 어댑터의 notifyItem.. 같은 메서드를 적용하면서 원하는 부분을 계속해서 개발자 스스로 확인하고 코드를 처리하는 부분은 불필요한 갱신시점과 코드 처리에 대한 귀찮음?을 발생시켰습니다.
이제는 ListAdapter의 DiffUtil 방식을 이용하여 편히 써봅시다
DiffUtil은 두 데이터셋을 받아서 그 차이를 계산해주는 클래스입니다. DiffUtil을 사용하면 두 데이터 셋을 비교한 뒤 그중 변한부분만을 파악하여 Recyclerview에 반영할 수 있습니다.
DiffUtil은 Eugene W. Myers의 difference 알고리즘을 이용해서 O(N + D^2)시간 안에 리스트의 비교를 수행하여 차이나 변한 부분을 감지하여 반영하도록 돕습니다.
AsyncListDiffer
DiffUtil은 아이템 개수가 많을 경우 비교연산에 필요한 시간이 길어질 수 있기 때문에 백그라운드 스레드에서 처리되어야 합니다. 사실 ListAdapter의 실제 구현 방식은 AsyncListDiffer가 내부에 구현되어 있다고 볼 수 있습니다. 그리고 우리가 ListAdapter 의 생성자를 통해 넘긴 DiffUtil.ItemCallback 객체도 AsyncListDiffer 에서 전달받아 사용합니다. 아래 코드를 보시면 더욱 자세히 확인이 가능합니다.
public abstract class ListAdapter<T, VH extends RecyclerView.ViewHolder>
extends RecyclerView.Adapter<VH> {
final AsyncListDiffer<T> mDiffer;
private final AsyncListDiffer.ListListener<T> mListener =
new AsyncListDiffer.ListListener<T>() {
@Override
public void onCurrentListChanged(
@NonNull List<T> previousList, @NonNull List<T> currentList) {
ListAdapter.this.onCurrentListChanged(previousList, currentList);
}
};
@SuppressWarnings("unused")
protected ListAdapter(@NonNull DiffUtil.ItemCallback<T> diffCallback) {
mDiffer = new AsyncListDiffer<>(new AdapterListUpdateCallback(this),
new AsyncDifferConfig.Builder<>(diffCallback).build());
mDiffer.addListListener(mListener);
}
@SuppressWarnings("unused")
protected ListAdapter(@NonNull AsyncDifferConfig<T> config) {
mDiffer = new AsyncListDiffer<>(new AdapterListUpdateCallback(this), config);
mDiffer.addListListener(mListener);
}
ListAdapter
AsyncListDiffer를 더 쓰기 편하도록 랩핑한 클래스로 Recyclerview 어댑터를 만들때 ListAdapter를 상속하도록 아래와 같이 하면됩니다.
class YourAdapter: ListAdapter<YourData, YourAdapter.YourViewHolder>(DIFF_CALLBACK) {
companion object {
private val DIFF_CALLBACK = object : DiffUtil.ItemCallback<YourData>() {
override fun areItemsTheSame(oldItem: YourData, newItem: YourData): Boolean {
return oldItem.commentId == newItem.commentId // Compare unique IDs
}
override fun areContentsTheSame(oldItem: YourData, newItem: YourData): Boolean {
return oldItem == newItem // Use .equals() method for full comparison
}
}
}
...
}
areItemsTheSame함수가 먼저 실행이 되고 해당 함수의 결과로 true 가 반환됐을 경우에만 areContentsTheSame이 호출됩니다
areItemsTheSame에서는 비교적 두 데이터 간의 고유한 값인 id로 비교하고 areContentsTheSame에는 아이템간의 정보가 같은지를 비교합니다
그리고 필수는 아니나 getChangePayload라는 메서드도 존재하는데 부분 업데이트 방식으로 해당 항목의 전체 내용을 다시 바인딩하지 않고 좋아요 개수만 업데이트 한다던가
전체 항목을 다시 바인딩하는 대신, 변경된 부분만 업데이트함으로써 성능을 최적화할 수 있습니다 그러므로 애니메이션이 끊이지 않고 실행되게합니다
override fun onBindViewHolder(holder: YourViewHolder, position: Int, payloads: MutableList<Any>) {
if (payloads.isNotEmpty()) {
val payload = payloads[0]
if (payload is String && payload == "좋아요 업데이트") {
// 좋아요 개수만 업데이트
holder.updateLikeCount()
return // 부분 업데이트 후 전체 바인딩을 건너뜁니다.
}
}
// 페이로드가 없거나 모든 데이터를 업데이트해야 하는 경우 전체 항목을 바인딩합니다.
onBindViewHolder(holder, position)
}