RecyclerView

뷰를 재활용하고 데이터를 리스트화해서 보여주는 컨테이너

RecyclerView

RecyclerView


뷰를 재활용하고 데이터를 리스트화해서 보여주는 컨테이너이다.

ListView 는 스크롤 시 화면에 보이는 만큼 뷰를 생성하고, 보이지 않으면 삭제하길 반복해서 리소스 낭비가 심하다는 단점이 있다.

이를 보완하기 위해 나온 RecyclerView 는 화면에 보이는 만큼 뷰 객체를 생성하고, 스크롤 시 새로 그려져야할 뷰가 있는 위치로 ViewHolder 를 이동시켜 재사용한다.

ViewHolder 는 화면에 표시될 아이템 뷰를 저장하는 객체로, 화면에 보이는 만큼 생성되고, 새로 그려져야할 뷰가 있다면 가장 위에 생성된 ViewHolder 를 재사용해서 데이터만 바꾼다.


RecyclerView 구성 요소


LayoutManager

RecyclerView 의 레이아웃을 결정하는 역할로, LinearLayoutManager, GridLayoutManager 등을 사용할 수 있다.

RecyclerView 자체는 자신이 어떻게 배치될지, 어떤 위치에 놓일지 모르기 때문에 LayoutManager 를 사용해서 아이템들을 적절히 배치하는 작업을 한다.

Adapter

RecyclerView 에 보여줄 데이터를 제공하고, 뷰를 생성하며, 뷰에 데이터를 바인딩하는 역할을 한다.

여기서 뷰를 생성한다는 것은 레이아웃을 Inflate 한 뷰 객체를 가지는 ViewHolder 를 메모리에 생성한다는 것을 의미한다.

class DataAdapter : RecyclerView.Adapter<DataViewHolder>() {
		
    override fun getItemCount(): Int = itemCount

    override fun onCreateViewHolder(
        parent: ViewGroup,
        viewType: Int
    ): DataViewHolder {
        return DataViewHolder(
            LayoutInflater.from(parent.context).inflate(
                R.layout.items_data,
                parent,
                false
            )
        )
    }

    override fun onBindViewHolder(
        holder: DataViewHolder,
        position: Int
    ) {
        get(position)?.let {
            holder.bind(it)
        }
    }
}
  • onCreateViewHolder()ViewHolder 를 생성하는 함수이다.
  • onBindViewHolder() 는 생성된 ViewHolder 에 데이터를 바인딩 해주는 함수이다.
  • getItemCount() 는 데이터의 총 갯수를 반환하는 함수이다.

ViewHolder

RecyclerView 의 아이템 뷰를 정하는 역할로, 생성된 ViewHolder 를 캐시하고 필요할 때 재활용하여 성능을 향샹사키는 역할을 한다.


RecyclerView 성능 향상


setHasStableIds()

RecyclerView 의 성능 향상 중 가장 기본은 onBindViewHolder() 의 호출을 최소화 하는 것이다.

setHasStableIds(true) 를 사용하면 각각 아이템 포지션에 지정된 id 를 기준으로 상황에 따라 onBindViewHolder() 호출을 제외시킨다.

값이 변경된 idonBindViewHolder() 를 호출하고, 호출된 아이템의 id 가 이전 포지션의 아이템에 이미 존재할 시 onBindViewHolder() 함수를 호출하지 않고 이전에 같은 id 를 가진 뷰를 대신 보여준다.

class RecyclerViewAdapter : RecyclerView.Adapter<...>() { 
    
    init {
        setHasStableIds(true)
    }

    /* ... */

    override fun getItemId(position: Int): Long =
        position.toLong() // or data id
}


setItemViewCacheSize()

ViewHolder 를 캐시에 저장해두면 스크롤 시 화면에서 UI 사려지고 다시 화면에 보여질 때 onBindViewHolder() 호출없이 보여지게 된다.

setItemViewCacheSize() 를 설정하면 스크롤 시 화면에 보이던 뷰가 사라질 때 사라진 뷰를 재활용하는 RecyclerViewPool 에 들어가지 않고 캐시에 저장할 수 있다.

private fun setAdapter() = with(binding.recyclerView) {
    recyclerViewAdapter = RecyclerViewAdapter()
    setItemViewCacheSize(10)
    adapter = recyclerViewAdapter
}


setHasFixedSize()

RecyclerView 는 데이터가 수정될 때 각각 아이템의 레이아웃을 다시 계산한다.

기본값인 setHasFixedSize(false) 로 설정되면 requestLayout() 이 호출되어 아이템의 레이아웃을 다시 계산한다.

setHasFixedSize(true) 로 설정하면 고정값으로 인식하기 떄문에 아이템의 UI 사이즈가 동일하다면 해당 설정을 하는 것이 성능 향상에 도움이 된다.

private fun setAdapter() = with(binding.recyclerView) {
    recyclerViewAdapter = RecyclerViewAdapter()
    setHasFixedSize(true)
    adapter = recyclerViewAdapter
}


setRecycledViewPool()

RecycledViewPool 은 여러 RecyclerView 사이에서 ViewHolder 를 공유하는 역할을 한다.

setRecycledViewPool() 설정하여 동일한 ViewType 을 공유하는 여러 RecyclerView 에서 ViewHolder 를 재사용할 수 있다.

ViewHolder 객체를 캐시하고 재활용하여 메모리 및 성능을 최적화할 수 있게 된다.

class RecyclerViewAdapter() : RecyclerView.Adapter<...>() {
    private val pool = RecyclerView.RecycledViewPool()
    val childAdapter1 = ChildAdapter()
    val childAdapter2 = ChildAdapter()

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ChildViewHolder =
        ChildViewHolder(
            ItemsChildBinding.inflate(LayoutInflater.from(parent.context), parent, false),
            pool
        )

    /* ... */
}

class ChildViewHolder(
    val binding: ItemsChildBinding,
    pool: RecyclerView.RecycledViewPool
) : RecyclerView.ViewHolder(binding.root) {

    init {
        val linearLayoutManager = LinearLayoutManager(itemView.context).apply {
            // 부모 RecyclerView 가 자식 RecyclerView 를 분리할 떄 뷰를 재활용할지에 대한 여부
            recycleChildrenOnDetach = true 
            orientation = LinearLayoutManager.HORIZONTAL
        }
        binding.recyclerView.layoutManager = linearLayoutManager
        binding.recyclerView.setRecycledViewPool(pool)
    }
}



ViewHolder 생성 과정


스크롤이 되면 RecyclerView 는 새로운 아이템 뷰를 보여줘야 한다고 LayoutManager 에게 알리며, LayoutManager 는 스크롤이 된 어떤 포지션에 아이템 뷰를 위치해야 하는지 계산하고, 이 위치에 구성할 아이템 뷰를 달라고 RecyclerView 에 요청한다.

RecyclerViewViewHolder 가 캐시에 저장되어 있다면, 해당 ViewHolderLayoutManager 에게 반환해준다.

ViewHolder 가 캐시에 저장되어 있지 않다면, Adapter 에게 해당 아이템 뷰의 ViewType 을 요청하고 받아온다.

RecyclerViewViewType 에 대한 ViewHolderRecyclerViewPool 에 있는지 확인한다.

ViewHolderRecyclerViewPool 에 있다면, RecyclerViewPool 에서 ViewHolder 를 반환하고, Adapter 에게 데이터를 바인딩하라고 요청하고, Adapter는 아이템 뷰를 LayoutManager 에게 전달한다.

ViewHolderRecyclerViewPool 에 없다면, Adapter 에게 ViewHolder 의 생성을 요청하고, Adapter 는 뷰를 생성하고, 데이터를 바인딩하고, 아이템 뷰를 LayoutManager 에게 전달한다.

LayoutManager 는 포지션에 해당하는 아이템 뷰를 배치한 후 RecyclerView 에게 알려주고, RecyclerViewAdapter 에게 아이템 뷰가 붙었다고 전달한다.

essential