패스트캠퍼스 강의를 통해 RecyclerView와 Adapter 연동을 실습하던 중, RecyclerView.ListAdapter를 적용하는 방법을 적용해보고, 기존의 RecyclerView.Adapter 사용할 때 보다 편리한 부분이 있어서 이를 좀 더 자세히 알아보려고 작성한다.
해당 뷰를 보여줄 Layout 안에 RecyclerView를 추가한다.
<!-- activity_main.xml -->
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"/>
RecyclerView로 보여줄 리스트 UI를 추가한다
<!-- item_value.xml -->
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:padding="24dp">
<TextView
android:id="@+id/valueTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
tools:text="value"
android:textSize="20sp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
class ValueAdapter(val list: MutableList<String>):
RecyclerView.Adapter(WordAdapter.ValueViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ValueViewHolder {
val inflater = parent.context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater
val binding = ItemValueBinding.inflate(inflater, parent, false)
return ValueViewHolder(binding)
}
override fun onBindViewHolder(holder: ValueViewHolder, position: Int) {
val value = list[position]
holder.bind(value)
}
override fun getItemCount() = list.size
class ValueViewHolder(private val binding: ItemValueBinding):
RecyclerView.ViewHolder(binding.root) {
fun bind(value: String) {
binding.apply {
valueTextView.text = value
}
}
}
}
private lateinit var valueAdapter: ValueAdapter
/* .. */
private fun initRecyclerView() {
valueAdapter = ValueAdapter(mutableListOf())
binding.recyclerView.apply {
// RecyclerView는 ListView와 GridView가 통합된 View로, layoutManager를 통해 이를 결정해야 함
// LinearLayoutManager, GridLayoutManager, StaggerGridLayoutManager
layoutManager = LinearLayoutManager(applicationContext, LinearLayoutManager.VERTICAL, false)
}
}
/* 이후 데이터셋이 변할 시 아래 메서드들을 통해 알림 */
valueAdapter.notifyDataSetChanged()
valueAdapter.notifyItemChanged(position)
valueAdapter.notifyItemRemoved(position)
valueAdapter.notifyInserted(position)
valueAdapter.notifyItemRangeInserted(positionStart, itemCount)
valueAdapter.notifyItemMoved(fromPosition, toPosition)
valueAdapter.notifyItemRangeChanged(positionStart, itemCount)
valueAdapter.notifyItemRangeRemoved(positionStart, itemCount)
그럼 ListAdapter는 무슨 차이가 있길래 편리하다고 느꼈을까? 비교를 통해 먼저 알아보자
RecyclerView에서 Adapter는 ViewHolder 객체를 생성하고, 해당 객체에 Data List를 주입하며, Data List의 변경사항을 UI에 반영한다. (notify...() 메서드를 통해)
이는 데이터를 업데이트하는 곳마다 notifyDataSetChanged() 등의 메서드를 사용해야하며, notify되는 범위를 세분화하여(Inserted, Changed 등등) 지연시간을 줄이는 방법을 사용하는 것도 까다롭다.
이를 해결하기 위해 리스트의 갱신을 직접 알리는 대신, 리스트의 차이를 알아내는 DiffUtil을 사용한다.
DiffUtil is a utility class that calculates the difference between two lists and outputs a list of update operations that converts the first list into the second one.
developer에 따르면 DiffUtil은 두 리스트간의 차이를 계산하고, 첫번째 리스트를 두번째 리스트로 변환하는 업데이트 작업 목록을 출력하는 유틸리티 클래스이다.
즉 기존 데이터 리스트와 업데이트된 데이터 리스트를 비교하여 차이를 알아낸다.
RecyclerView.Adapter를 사용할 때와 동일하게 RecyclerView와 List Layout을 추가한다.
public abstract class ListAdapter<T, VH extends RecyclerView.ViewHolder> extends RecyclerView.Adapter
-> ListAdapter<데이터 클래스, 뷰홀더>(콜백)
RecyclerView.Adapter base class for presenting List data in a RecyclerView, including computing diffs between Lists on a background thread.
This class is a convenience wrapper around AsyncListDiffer that implements Adapter common default behavior for item access and counting.
ListAdapter에서 내부적으로
👉 백그라운드 스레드에서 리스트 변경 사항이 계산되면 사용자는 업데이트 된 RecyclerView를 볼 수 있음
리스트의 변경 사항은 DiffUtil.ItemCallback을 사용하여 차이를 계산함
DiffUtil.Callback : 데이터 리스트를 비교해서 차이를 계산하고 콜백 해주는 클래스
class ValueAdapter: ListAdapter<String , RecyclerView.ViewHolder>(
// 데이터가 변경됨을 체킹하기 위해 Implement한 메서드 -> RecyclerView와 달리 알아서 UI 처리
object : DiffUtil.ItemCallback<String>() {
override fun areItemsTheSame(oldItem: ImageItems, newItem: ImageItems): Boolean {
// Identity, 같은 값을 참조하고 있는지 체크 (동등성 체크)
return oldItem === newItem
}
override fun areContentsTheSame(oldItem: ImageItems, newItem: ImageItems): Boolean {
// Equality, 같은 값인지 체크 (동일성 체크)
return oldItem == newItem
}
}
) {
override fun getItemCount(): Int {
return currentList.size
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
val inflater = parent.context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater
val binding = ItemValueBinding.inflate(inflater, parent, false)
return ValueViewHolder(binding)
}
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
holder.bind(currentList[position])
}
}
private lateinit var valueAdapter: ValueAdapter
/* .. */
private fun initRecyclerView() {
valueAdapter = ValueAdapter()
binding.recyclerView.apply {
adapter = valueAdapter
layoutManager = LinearLayoutManager(context)
}
}
ListAdapter에서 사용할 수 있는 메서드는
기존의 Adapter와 ListAdapter를 함께 사용한 실습 예제는 링크를 통해 확인