리사이클러 뷰에 데이터셋이 추가되어서 notifyDataSetChanged() 를 사용하는 경우가 흔하다. 하지만 리사이클러뷰를 업데이트하는 함수가 이것뿐만 있는게 아니고, 이 함수를 남발할 경우 성능에 악영향을 줄 수 있다.
특정 항목만 갱신(애니메이션, 업데이트)할 때 사용할 수 있다.
파라미터 payload 는 어댑터의 onBindViewHolder() 가 호출될 때 넘겨받는 객체이다.
payload 를 고려하지 않은 onBindViewHolder 가 아니라,
fun onBindViewHolder(holder: TaskItemViewHolder, position: Int)
이러한 onBindViewHolder 를 따로 정의해 줘야한다.
onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position, @NonNull List<Object> payloads)
onBindViewHolder() 는 payload 를 받지 않거나, null 을 받는다면 view 를 초기하 해주는 로직이 들어간다. 그 외의 경우는 따로 처리를 해주면 된다. 가령 예를 들면 이런 경우다.
@Override
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position, @NonNull List<Object> payloads) {
if (payloads.isEmpty()) {
super.onBindViewHolder(holder, position, payloads);
}else {
for (Object payload : payloads) {
if (payload instanceof String) {
String type = (String) payload;
if (TextUtils.equals(type, "click") && holder instanceof TextHolder) {
TextHolder textHolder = (TextHolder) holder;
textHolder.mFavorite.setVisibility(View.VISIBLE);
textHolder.mFavorite.setAlpha(0f);
textHolder.mFavorite.setScaleX(0f);
textHolder.mFavorite.setScaleY(0f);
//animation
textHolder.mFavorite.animate()
.scaleX(1f)
.scaleY(1f)
.alpha(1f)
.setInterpolator(new OvershootInterpolator())
.setDuration(300);
}
}
}
}
}
payload 파라미터로 String "click" 이 들어온 경우에 대해서 애니메이션을 처리해준다.
DiffUtil 클래스는 두 list 사이의 차이를 찾아내는데 특화된 클래스이다.
리사이클러 뷰 어댑터 클래스의 상속 구조 자체를 바꿔야하는 방법이다. 그 부분이 다소 낯설 수 있지만, 그점만 감내한다면 별도의 코드 처리 없이 데이터셋 변화에 대해 효과적으로 대응할 수 있다.
우선 DiffUtil.ItemCallback<T>
를 상속하고,
areItemsTheSame(T oldItem, T newItem) 와 areContentsTheSame(T oldItem, T newItem) 를 구현해야한다. 각각은 두 item 들이 동일한 item 을 나타내는지, 두 item 이 동일한 데이터를 갖는지 구분하는 방법을 구현하는 함수이다.
class TaskDiffItemCallback: DiffUtil.ItemCallback<Task>() {
// 두 변수가 동일한 item 을 참조하고 있는지 판단한다.
override fun areItemsTheSame(oldItem: Task, newItem: Task): Boolean =
oldItem.taskId == newItem.taskId
// 두 object 가 동일한 content 를 가졌는지 판단한다.
// Task 클래스가 data 클래스이므로 이렇게 체크하면 됨.
override fun areContentsTheSame(oldItem: Task, newItem: Task): Boolean =
oldItem == newItem
}
그리고 리사이클러뷰 어댑터가 ListAdapter(@NonNull DiffUtil.ItemCallback<T> diffCallback)
를 상속하도록 바꿔야한다.
class TaskItemAdapter:
ListAdapter<Task, TaskItemAdapter.TaskItemViewHolder>(TaskDiffItemCallback()) { ... }
이제는 getItemCount 도 필요없고, 자체적으로 갖는 data 변수도 필요하지 않다.
class TaskItemAdapter : RecyclerView.Adapter<TaskItemAdapter.TaskItemViewHolder>() {
var data = listOf<Task>()
set(value) {
field = value
notifyDataSetChanged()
}
override fun getItemCount(): Int = data.size
class TaskItemViewHolder(val rootView: CardView) : RecyclerView.ViewHolder(rootView) {
val taskName = rootView.findViewById<TextView>(R.id.task_name)
val taskDone = rootView.findViewById<CheckBox>(R.id.task_done)
companion object {
fun inflateFrom(parent: ViewGroup): TaskItemViewHolder {
val layoutInflater = LayoutInflater.from(parent.context)
val view = layoutInflater.inflate(R.layout.item_task, parent, false) as CardView
return TaskItemViewHolder(view)
}
}
fun bind(item: Task) {
taskName.text = item.taskName
taskDone.isChecked = item.taskDone
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TaskItemViewHolder =
TaskItemViewHolder.inflateFrom(parent)
override fun onBindViewHolder(holder: TaskItemViewHolder, position: Int) {
val item = data[position]
holder.bind(item)
}
}
class TaskItemAdapter:
ListAdapter<Task, TaskItemAdapter.TaskItemViewHolder>(TaskDiffItemCallback()) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TaskItemViewHolder =
TaskItemViewHolder.inflateFrom(parent)
override fun onBindViewHolder(holder: TaskItemViewHolder, position: Int) {
// ListAdapter 에서 제공되는 함수
val item = getItem(position)
holder.bind(item)
}
class TaskItemViewHolder(val rootView: CardView) : RecyclerView.ViewHolder(rootView) { ... }
}
변경 전
viewModel.tasks.observe(viewLifecycleOwner, Observer {
it?.let {
adapter.data = it
}
})
변경 후
viewModel.tasks.observe(viewLifecycleOwner, Observer {
it?.let {
adapter.submitList(it)
}
})