24.01.23 ListAdapter

KSang·2024년 1월 24일
0

TIL

목록 보기
38/101

개요

리사이클러뷰는 타입별로 나눠 다양한 레이아웃을 꾸밀 수 있다

리소스를 남기지 않고 재사용하기 때문에 화면의 대부분의 UI는 리사이클러뷰로 꾸며주는게 좋다

지금까진 리사이클러뷰를 구성할때 RectclerView Adapter를 사용했는데 내용 갱신할땐 notifyItem메소드를 사용해서 내용을 갱신했다

notifyItemChanged(int)
notifyItemInserted(int)
notifyItemRemoved(int)
notifyItemRangeChanged(int, int)
notifyItemRangeInserted(int, int)
notifyItemRangeRemoved(int, int)

하지만 이런 방법으로 사용하면 변경될때마다 일일이 변경되는 방식을 확인하고 notify를 정해줘야하는데 이게 참 번거롭고 갱신이 필요없는 홀더도 같이 갱신되는경우가 있다

그런데 ListAdapter가 나오면서 이걸 해결 할 수 있게 됬다.

ListAdapter

DiffUtill

DiffUtill은 두 데이터셋을 받아 그 차이를 계산해준다.

두 데이터 셋을 비교한 뒤 변한 부분만 파악해서 리사이클러뷰에 반영해준다

리사이클러뷰어댑터를 리스트 어댑터로 바꿔야할 가장 큰 이유라고 생각하는데 notify의 불편한점을 개선해 편하고 효율적으로 만들 수 있다

val TodoModelDiffCallback = object : DiffUtil.ItemCallback<TodoModel>() {
            override fun areItemsTheSame(oldItem: TodoModel, newItem: TodoModel): Boolean {
                return oldItem.key == newItem.key
            }

            override fun areContentsTheSame(oldItem: TodoModel, newItem: TodoModel): Boolean {
                return oldItem == newItem
            }
        }

areItemsTheSame에서 두 데이터 셋을 식별자로 비교해 같은 데이터가 맞는지 확인하고

areContentsTheSame에서 전체적인 데이터가 변한게 있는지 확인한다.

시간 복잡도는 O(N + D^2) D = 스크립트의 길이 라고한다

ListAdapter

이제 위에서 정의한 DiffUtill을 가지고 리스트 어댑터를 만들어주는데

리사이클러뷰를 상속받기 때문에 구성이 유사하다

class TodoListAdapter :
    ListAdapter<TodoModel, TodoListAdapter.Holder>(TodoModelDiffCallback) {

    interface ItemClick {
        fun onClick(view: View, position: Int, item: TodoModel)
    }

    var itemClick: ItemClick? = null

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): Holder {
        val binding =
            TodoListItemBinding.inflate(LayoutInflater.from(parent.context), parent, false)
        return Holder(binding, itemClick)
    }

    override fun onBindViewHolder(holder: Holder, position: Int) {
        val item = getItem(position)
        holder.setView(item)
    }

    class Holder(private val binding: TodoListItemBinding, private val itemClick: ItemClick?) : RecyclerView.ViewHolder(binding.root) {
        fun setView(item: TodoModel) = with(binding) {
            title.text = item.title
            description.text = item.content
            bookmark.isChecked = item.bookmark
            root.setOnClickListener {
                itemClick?.onClick(it, adapterPosition, item)
            }

            bookmark.setOnClickListener {
                itemClick?.onClick(it, adapterPosition, item)
            }
        }
    }

    companion object {
        val TodoModelDiffCallback = object : DiffUtil.ItemCallback<TodoModel>() {
            override fun areItemsTheSame(oldItem: TodoModel, newItem: TodoModel): Boolean {
                return oldItem.key == newItem.key
            }

            override fun areContentsTheSame(oldItem: TodoModel, newItem: TodoModel): Boolean {
                return oldItem == newItem
            }
        }
    }
}

ListAdapter는 백그라운드 스레드에서 DiffUtil을 실행하기 때문에, UI 스레드에서 복잡한 작업을 수행하여 앱의 반응성을 저하시키는 것을 방지한다.

currentList로 현재 데이터를 불러올 수 있고, submitList로 데이터를 갱신할 수 있다

Recyclerview 어댑터를 ListAdapter로 구현하면

데이터가 어떻게 바뀌든간에 submitList로 전체 리스트를 넘겨줄때

어댑터가 알아서 백그라운드 스레드를 사용해 리스트 차이를 계산하여 화면을 갱신시켜준다

submitList같은 경우 라이브 데이터를 옵저빙해서 프래그먼트 혹은 액티비티에 이런식으로 넣어주면 된다

class TodoListFragment : Fragment() {

    ...
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        initView()
        initViewModel()
    }

    private fun initView() {
        setAdapter()
    }

    private fun initViewModel() = with(viewModel) {
        uiState.observe(viewLifecycleOwner) {
            adapter?.submitList(it.list)
        }
    }
    ...

주의사항

submitList로 리스트를 받고 DiffUtill에서 확인하는데 어댑터 내에서 데이터가 변하면 인지 할 수 없으니 주의 해야한다.

뷰 초기화 중에 어댑터를 다시 생성하지 않도록 onCreateView또는 에서 어댑터 onViewCreated를 초기화하고 할당하는 것이 좋다

ListAdapter기본적으로 항목 변경 시 간단한 페이드인/페이드아웃 애니메이션을 사용한다. RecyclerView.ItemAnimator보다 정교한 효과를 사용하여 항목 애니메이션을 사용자 정의할 수 있다.

Data Binding 또는 View Binding을 사용하는 경우 메모리 누수를 방지하기 위해 ListAdapter바인딩의 수명 주기를 의 호스트 수명 주기에 바인딩해야 한다.

0개의 댓글