리사이클러뷰는 2년 전에 SOPT 라는 동아리에서부터 접해보았고, DiffUtil 과 ListAdapter 에 대해 알고 난 이후로부터는 notifyXXX
함수들을 등한시하게 되었다.
사실 ListAdapter 도 얼마 쓰지 않았다.
바로, 컴포즈로 넘어갔기 때문 😂
하지만, 우테코에서는 컴포즈가 아닌 기존 Android View 시스템을 사용하고 있고, RecyclerView 는 View 시스템을 사용한다면 뗄레야 뗄 수 없는 친구이기에 잘 알고 있어야 한다.
그리고, 저번 수업시간에 우테코 코치 레아는 만약 자신이 면접관이라면 리사이클러뷰에 대해서는 물어볼 것이다..! 라고 하셨기 때문에 더욱 공부하고 정리해야겠다는 마음을 먹었다 💪
RecyclerView 에 대해 딥하게 공부를 하다보니 생각보다 이 친구 쉽지 않는 녀석이였다. (아니 진짜 개어려운데..?)
그래서, 우테코 레벨 2가 끝나기 전에 리사이클러뷰와 View 시스템(리사이클러뷰를 잘 알려면 View 에 대해 잘 알아야한다)에 대해 딥다이브 하고 정리해볼 계획이다.
아.. 블로그 글 쓰다가 중단된 거 많은데 언제 다쓰지..?
그리고, 이번주 블로그글을 뭘 쓸지 고민하다가 이번 쇼핑 카트
미션에서 ListAdapter
와 DiffUtil
을 금지 당하기도 했고, 리사이클러뷰를 최적화하는 가장 쉬운 방법이 notifiyXXX
함수들 적절히 사용하는 것이기에 정리하고자 한다.
fun updateProduct(newProducts: List<CartProductUi>) {
products = newProducts
notifyDataSetChanged()
}
보통 RecyclerView나 ListView 를 사용해본 사람이라면
notifyDataSetChanged()
를 통해 뷰를 업데이트했을 것이다.
사용하기도 매우 쉽고, 네이밍도 직관적이다.
Adapter야 나 데이터 바뀜, 이대로 반영해줘!
그러나, notifyDataSetChanged()
함수를 사용하면 비효율적이기 때문에 안스에서 아래와 같이 린트를 뿜는다.
notifyDataSetChanged() 를 사용하게 되면 모든 아이템 View 들이 다시 bind 및 layout 된다. (다시 그려지고, 부모 뷰 그룹에 다시 붙는다는 말이다.)
만약, 100개의 List 가 있다고 가정을 해보자.
난 2번 item과 20 번 item 을 Swiping 하고 싶다.
그렇다면, 2번과 20번 item 만 다시 그리는 것이 제일 효율적일 것이다.
그러나, notifyDataSetChanged()
함수는 100개의 item을 다 다시 그리고 재배치시킨다.
notifyDataSetChanged()
함수는 정말 최후의 순간에 사용해야할 것이다.
레아는 notifyDataSetChanged() 를 써야할 때가 있다고 한다. 하지만, 그게 언제인지 아직 잘 모르겠다 😅 알게 되면 추가로 포스팅하겠다..!
리사이클러뷰 데이터 변경 이벤트에는 다음과 같이 크게 2가지로 분류된다.
1) item 변경
: 단일 item의 데이터만 업데이트하는 경우
2) 데이터 구조 변경
: 단일 item 을 삽입, 제거 또는 이동하여 데이터의 위치나 구조가 변경될 경우
데이터 변경 이벤트에 따라 최적화하도록 RecyclerView.Adapter 에서는 다양한 notifyXXX()
함수들을 제공한다.
item 변경
notifyItemChanged(postion: Int)
notifyItemRangeChanged(int positionStart, int itemCount)
notifyItemRangeChanged() 을 활용하여 position
에 해당하는 item 의 Data 만 업데이트하여 다시 그려줄 수 있다!
fun updateItem(position: Int, newItem: Product) {
items[position] = newItem
notifyItemChanged(position)
}
만약, 여러 position 에서 data 가 변경됐을 경우에는 notifyItemRangeChanged
를 사용하자!
public final void notifyItemChanged(int position) {
mObservable.notifyItemRangeChanged(position, 1);
}
참고로, notifyItemChanged은 내부에서notifyItemRangeChanged을 호출하고 있다.
데이터 구조 변경
notifyItemInserted(position)
notifyItemRangeInserted(position, itemCount)
특정 position
에 data를 삽입하거나 추가해주고 싶다면 notifyItemInserted(position)
를 활용해주면 된다.
fun addItem(item: Product) {
items.add(item)
notifyItemInserted(items.size - 1)
}
만약, 여러 데이터를 추가해주고 싶다면 notifyItemRangeInserted 를 사용하자!
해상 영상은 notifyItemRangeInserted
을 활용한 것이다 😸
notifyItemRemoved(position)
notifyItemRangeRemoved(position, itemCount)
특정 position
에 data를 삭제하고싶다면 notifyItemRemoved(position)
를 활용해주자
notifyItemMoved(fromPosition, toPosition)
해당 notifyItemMoved 는 drag & drop
과 같은 특수한 상황에서 사용하는 함수이다.
1 번 위치를 4번 위치와 drag & drop
으로 swipe 하고 싶다면 해당 함수와 ItemTouchHelper
를 함께 사용하면 된다.
fun moveItem(fromPosition: Int, toPosition: Int) {
if (fromPosition < toPosition) {
for (i in fromPosition until toPosition) {
Collections.swap(items, i, i + 1)
}
} else {
for (i in fromPosition downTo toPosition + 1) {
Collections.swap(items, i, i - 1)
}
}
notifyItemMoved(fromPosition, toPosition)
}
필자는 과거에 프로젝트에서 item의 순서를 수동으로 바꿔야하는 경우가 있어 사용해본 적이 있는데, 요즘은 해당 UX/UI 가 사용되는 앱을 찾아보기 힘들다 😅
지금까지, RecyclerView의 data set을 효율적으로 변경하는 notifyxxx
함수들에 대해 배웠다.
그러나, 개발자가 Adapter data 를 매번 상황에 맞게 해당 적절한 함수를 사용하고, data를 조작하는 일은 굉장히 피곤하다..
따라서, 필자는 DiffUtil & ListAdapter 를 사용할 것을 적극 권장하지만 그래도 해당 함수들을 한 번씩 사용해보고 리팩토링하는 경험을 해보는 것을 추천한다!
notifyxxx
함수들을 사용해보면서 어떠한 이유로 DiffUtill 이라는 개념이 나왔는지 체감하게 된다면 RecyclerView 와 DiffUtil 에 대한 이해도가 늘어 날 것이기 때문인다.
필자는 우테코 쇼핑 장바구니 미션에서 DiffUtil 을 참고해서 ItemUpdateHelper
라는 Util class 를 만들었다.
심심하신 분들은 해당 file 봐주세요.. 🙇♂️