안드로이드 개발에 있어 RecyclerView는 빼놓을 수 없는 요소가 되었습니다. 그 이유는 기존에 사용됐던 ListView에 비해RecyclerView 성능 및 효율이 좋기 때문입니다. RecyclerView를 정의하면 아래와 같습니다.
RecyclerView란? 사용자가 관리하는 많은 수의 데이터 집합(Data Set)을 개별 아이템 단위로 구성하여 화면에 출력하는 뷰그룹(ViewGroup)이며, 한 화면에 표시되기 힘든 많은 수의 데이터를 스크롤 가능한 리스트로 표시해주는 위젯입니다.
이제부터 기본적인 RecyclerView의 사용법 및 여러가지 기능에 대해 알아보도록 하겠습니다.
<소스코드 1>
class MyAdapter:
RecyclerView.Adapter<MyAdapter.MyViewHolder>() {
var items: ArrayList<MyData> = ArrayList()
inner class MyViewHolder(itemView: View): RecyclerView.ViewHolder(itemView) {
fun setData(data: MyData) {
// itemView를 이용해 데이터 세팅
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
val itemView = LayoutInflater.from(parent.context)
.inflate(R.layout.item, parent, false)
return MyViewHolder(itemView)
}
// ViewHolder에 데이터 전달 및 position별 데이터 결합
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
holder.setData(items[position])
}
// 반환할 데이터 개수
override fun getItemCount(): Int {
return items.size
}
}
RecyclerView의 핵심은 Adapter와 ViewHolder 에 있습니다. RecyclerView에서는 ViewHolder 패턴이 강제되기 때문에 뷰의 재활용을 통해 성능을 개선할 수 있습니다. 간단히 주요 메소드에 대해 알아보면
- onCreateViewHolder(ViewGroup parent, int viewType) : viewType 형태의 아이템 뷰를 위한 뷰홀더 객체를 생성.
- onBindViewHolder(ViewHolder holder, int position) : position에 해당하는 데이터를 뷰홀더의 아이템 뷰에 표시.
- getItemCount() : 전체 아이템 개수를 리턴.
<소스코드 2>
val rv = findViewById<RecyclerView>(R.id.recycler_view)
val adapter = MyAdapter(this)
// adapter에 사용할 데이터 세팅
// adapter.items = ...
rv.adapter = parentAdapter // set adapter
rv.layoutManager = LinearLayoutManager(this) // vertical? horizontal?
Adapter와 ViewHolder를 생성했다면 마지막으로 RecyclerView 객체를 생성하고 만든 Adapter를 연결만 해주면 사용할 수 있습니다. 추가적으로 LayoutManger를 통해 스크롤 방향을 지정할 수 있고, Item Decoration을 이용해 구분선이나 간격등 아이템에 대한 동적인 decoration이 가능합니다.
여기까지가 RecyclerView에 대한 간단한 사용법입니다.
이 뒤부터는 RecyclerView를 좀 더 효율적으로 사용하는법에 대해 소개하겠습니다. 저같은 경우 이부분을 간과하고 RecyclerView를 사용했던 것 같습니다. 비록 요즘들어 디바이스의 성능이 점점 좋아져서 큰 성능의 개선을 느끼지 못할 수도 있지만, 현재 많은 앱에서 수많은 데이터의 사용이 이뤄지기 때문에 중요한 대목이라고 생각합니다.
1. RecyclerView의 갱신
흔히 RecyclerView에서 데이터 갱신이 이뤄질때 notifyDataSetChanged()를 많이 호출하곤 합니다. 하지만 이는 리스트 내 모든 데이터를 초기화 하기때문에 불필요한 뷰도 다시 그려질 수 있어 비효율적입니다. 따라서 아래와 같은 메소드를 상황에 맞춰 사용하시면 성능개선에 도움을 줄 수 있습니다!
- 갱신될 위치를 알고있을때
- notifyItemChanged(int position) : position 위치에만 다시 그림.
- notifyItemRangeChanged(int positionStart, int itemCount) : position 위치부터 itemCount만큼 다시 그림.
- notifyItemRangeInserted, notifyItemRangeRemoved 등.. - 갱신될 위치를 모를때
- DiffUtil 을 사용해 리스트의 변경된 부분만 갱신.
2. setHasFixedSize
recyclerView.setHasFixedSize(true)
setHasFixedSize()는 RecyclerView의 크기를 고정하겠다는 의미로 해당 함수를 true로 설정하게 되면 Adapter 내용이 바뀌어 갱신될때 RecyclerView.onMeasure에서 변경된 크기 값을 알아내는 로직없이 갱신이 이뤄질 수 있습니다.
(Adapter의 내용에 따라 크기가 변경된다면 false로 설정합니다.)
3. setItemViewCacheSize
recyclerView.setItemViewCacheSize(int size)
setItemViewCacheSize()는 스크롤되어 화면에 사라지는 뷰에 대해 재사용되는 recycled view pool에 들어가지 않고, Cache에 저장되어 다시 화면에 나타날때 onBindViewHolder() 호출 없이 그대로 보여주게 해줍니다.
어? 그렇다면 ViewHolder를 재사용하는 RecyclerView 구조상 같은 맥락 아닌가라는 생각이 들 수 있습니다. 여기서 포인트는 RecyclerView는 ViewHolder 객체를 재사용하는 것이지, 객체 내 데이터까지 재사용하지는 않습니다. 따라서 위 메소드 호출이 없다면 onBindViewHolder()의 재호출이 발생합니다!
4. setHasStableIds
adapter.setHasStableIds(true)
setHasStableIds()를 true로 설정하게 되면 같은 id를 가지는 아이템을 onBindViewHolder()의 재호출 없이 최초 만든 View를 보여줍니다.
단, 해당 메소드를 사용하기 위해 Adapter내 getItemId()를 상속받아야합니다! (id로는 고정값을 설정하시면 됩니다.)
override fun getItemId(position: Int): Long {
return item[position].id.toLong();
}
5. 중첩된 RecyclerView에서의 Pool 공유
class OuterAdapter:
RecyclerView.Adapter<OuterAdapter.OuterViewHolder>() {
val sharedPool = RecyclerView.RecyclerViewPool()
...
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): OuterViewHolder {
val itemView = LayoutInflater.from(parent.context)
.inflate(R.layout.item, parent, false)
val innerRV = itemView.findViewById(R.id.inner_recycler_view)
innerRV.setRecycledViewPool(outerRV.recycledViewPool)
innerRV.layoutManager = LinearLayoutManager(context)
.apply { recycleChildrenOnDetach = true }
return MyViewHolder(itemView)
}
...
동일한 뷰에 대해 중첩으로 RecyclerView가 구성되있을때 바깥 쪽 RecyclerView의 Pool을 안쪽 RecyclerView Pool에 공유해 성능 높여줍니다. setRecyclerViewPool()을 이용해 바깥 쪽 RecyclerViewdml Pool을 공유하며, 추가적으로 LayoutManager 설정에서 recycleChildrenOnDetach()를 true로 설정합니다.
6. 초기화는 ViewHolder에서만..!
onBindViewHolder()에서는 아이템 위치(position)를 주기 때문에 무심코 리스너 설정을 해당 메소드에서 구현할 수 있습니다. 하지만 RecyclerView의 구조상 ViewHolder는 재사용하나 onBindViewHolder()는 재호출이 이뤄지기 때문에 비효율적인 코드가 될 수 있으므로, onBindViewHolder()에서는 데이터 전달만하고 리스너 설정은 ViewHolder 내에서 구현하면 성능 개선에 도움이 될 수 있습니다.
그렇다면 ViewHolder내에서 아이템의 위치는 어떻게 알 수 있을까요? 다행히 getAdapterPosition()이라는 메소드를 지원하기 때문에 충분히 구현할 수 있습니다!
inner class MyViewHolder(itemView: View):
RecyclerView.ViewHolder(itemView) {
var binding = ItemBinding.bind(itemView)
init {
// 리스너설정은 최초 초기화시에만
// onBindViewHolder()에서 하게되면 불필요한 리스너 설정이 지속됨.
binding.container.setOnClickListener {
if(adapterPosition != RecyclerView.NO_POSITION) {
val item = items[adapterPosition]
...
}
}
}
...
오늘은 RecyclerView에 대해 간단하게 남아 알아보았습니다. 무심코 사용했던 RecyclerView에 대해 자세히 알아보니 여러 다양한 기능이 있었고, 추가적으로 성능 개선을 할 수 있는 방법이 있다는 사실을 알 수 있었습니다. 비록 짧은 식견과 부족한 실력이지만 도움되셨다면 다행입니다.
감사합니다:)
* 참고 : https://jaeryo2357.tistory.com/70
[Android] 뷰의 성능 개선 - RecyclerView
들어가기 전 이번 포스팅에서는 Android 앱에서 가장 많이 사용한다고 볼 수 있는 RecyclerView의 성능 개선에 대해서 알아보자. 하지만 먼저 알아야할 도구가 있다. 바로 GPU 렌더링 막대기 개발자 옵
jaeryo2357.tistory.com
https://recipes4dev.tistory.com/154
안드로이드 리사이클러뷰 기본 사용법. (Android RecyclerView)
1. 안드로이드 리사이클러뷰(RecyclerView) 리사이클러뷰(RecyclerView)는, "많은 수의 데이터 집합을, 제한된 영역 내에서 유연하게(flexible) 표시할 수 있도록 만들어주는 위젯"입니다. [안드로이드 개발
recipes4dev.tistory.com
'Android' 카테고리의 다른 글
[안드로이드 / Kotlin] 저전력 블루투스 BLE(1), BLE란? (0) | 2022.02.27 |
---|---|
[안드로이드 / Kotlin] 진동 (0) | 2022.02.13 |
[안드로이드 / Kotlin] Retrofit2 (0) | 2022.01.15 |
[안드로이드 / Kotlin] 생체 인식 인증 사용하기 (0) | 2022.01.07 |
[안드로이드 / Kotlin] 카카오 로그인 SDK v2 사용하기 (0) | 2022.01.07 |