[Kotlin Android] RecyclerView 구현 기초

이현우·2020년 7월 22일
4

Android 기능 구현

목록 보기
5/13
post-thumbnail
post-custom-banner

2021년 4월 10일 수정 (View(Data)Binding 사용)

What is RecyclerView?

recycle
1. (폐품을) 재활용하다
2. (같은 생각, 방법 등을) 다시 이용하다

인스타그램, 유튜브 피드와 같이 동일한 형태의 뷰에 데이터에 따라서 달라지는 형태의 뷰를 (틀을) 재활용하는 뷰, 리사이클러뷰(RecyclerView)라 한다.

그렇다면 리사이클러 뷰에서 중요한 점은 다음과 같을 것이다.

  1. 어떠한 형태로 들어갈 것인지 [반복되는 틀 Layout과 배치 형태 결정]
  2. 어떠한 데이터가 들어갈 것인지 [데이터 형태 결정]
  3. 어디에 어떤 데이터가 들어갈 것인지 [ViewHolder 제작]
  4. 리사이클러뷰에 어떻게 데이터들을 연결시킬 것인지 [Adapter 제작]

이런 문제를 해결해나가면서 문제를 해결하도록 하자. 필자가 제작했던 뷰를 베이스로 하여 리사이클러뷰를 제작해보자.

RecyclerView 제작

1. 데이터는 이런 형태로 들어갔으면 좋겠어요

데이터가 들어갈 Item Layout 결정

item_product_review.xml

반복되는 형태의 틀을 기존 Layout 제작하는 방식과 마찬가지로 동일하게 제작하면 된다. Constraint, Relative 등 Layout의 종류에 얽매이지 않고 제작하면 된다.

Layout이 배열될 형태, LayoutManager 결정

리사이클러뷰내에서 위와 같이 선형으로 배열(LinearLayoutManager)을 할 지, 바둑판 형식(GridLayoutManager)으로 배열할 지를 결정하면 된다.
<androidx.recyclerview.widget.RecyclerView
            app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
            android:orientation="vertical"
            레이아웃매니저를 LinearLayoutManager로 설정하고 배열방식을 vertical로 설정하면 데이터들을 리스트형식으로 배열한다
            app:layout_constraintTop_toBottomOf="@+id/spn_search_goods_filter"
            listitem을 적용하면 리사이클러뷰 내에서 위의 Layout 형태로 뷰를 미리볼  있음
            tools:listitem="@layout/item_search_main_goodssearch" />

2. Layout에 이런 데이터가 들어갔으면 좋겠어요

Layout에 들어갈 Data의 형태를 Data Class로 결정한다

위의 사진을 보면 레이아웃의 형태는 결정되었지만 안에 들어갈 내용은 뭐가 들어갈 지 모르는 상황이다. 그렇다면 들어가는 내용의 형태를 개발자가 미리 결정해야 하는데, Kotlin에서는 이를 data class로 제작하여 결정한다.

data class는 말 그대로 "데이터의 형태"를 결정시켜주는 틀의 기능을 하는 클래스라 보면 된다. 이런 데이터 클래스는

  • equals(데이터 간 비교 가능)
  • hashCode(객체의 해쉬 값 도출)
  • toString(객체를 String으로 캐스팅할 때 내부 데이터 출력 가능)
  • copy(깊은 복사)

등의 함수를 자동으로 생성할 수 있게 한다.

위의 Layout에는 사진, 제품 이름, 나이, 제품 리뷰, 평점, 하트 등의 데이터가 들어가야 하므로 필자는

data class LayoutData (
    val img_goods : String,
    val tv_goods : String,
    val tv_age : String,
    val tv_review : String,
    val tv_star : Float,
    val tv_heart : Float
)

의 형식으로 데이터 클래스를 작성했을 것이다.

3. 이 데이터들은 Layout에 제가 원하는 방식으로 들어가면 좋겠어요

데이터는 결정되었어! 근데 Layout에는 어떻게 연결시켜?

이제 반복적으로 데이터가 들어갈 틀과, 들어갈 데이터의 형식이 결정되었다. 그러나 이 둘을 연결을 시키는 과정이 지금까지 내용에서 다룬적이 없다.
이런 기능을 처음 구현한다고 하면 각 아이템 마다 뷰를 찾아서(findViewById함수 이용하겠죠) 데이터를 일일이 매칭을 할 것이다. 그게 가장 간편하고 쉽게 떠올릴 수 있는 방안이기 때문이다.
그러나 RecyclerView에서는 ViewHolder라는 아주 획기적인 방법으로 노가다는 줄이고 효율성을 극대화하여 위의 기능을 구현할 수 있다.

그럼 ViewHolder는 뭔데?

ViewHolder는 클래스 내에 View를 저장하는 변수를 만들어 그 안에 데이터를 직접 연결시킬 수 있는 클래스, 디자인 패턴을 말 말한다. 아마 이렇게 말하면 이해가 한 번에 와닿지 않는 독자들이 많을 것이다. 왜냐하면 내가 그랬기 때문이다. 그림으로 다시 보자

위와 같이 내가 넣고자 하는 데이터(데이터 클래스 형식으로 저장된 변수)를 실제 레이아웃의 데이터로 연결시키는 기능을 하는 것이 ViewHolder이다.
그럼 ViewHolder에서는 다음과 같은 것들이 필요할 것이다.

  1. View를 저장할 수 있는 변수
  2. View와 데이터를 연결시키는 기능(함수)

그럼 ViewHolder 클래스는 다음과 같은 형식으로 작성할 수 있다. 물론 1, 2 외에도 필요한 것이 있지만 우선 위의 사항들을 중점으로 클래스를 분석해보자

class MyViewHolder(private val binding: ViewDataBinding) : RecyclerView.ViewHolder(binding,root) {
    //View와 데이터를 연결시키는 함수
   fun bind(layoutData : LayoutData){
    	binding.tvGoods.text = layoutData.tv_goods
    	...
    }

위와 같이 데이터들을 View에 넣을 수 있다.

4. 내가 제작한 Layout들을 실제 View에 연결하고 싶어요

Layout들과 RecyclerView를 연결시킬 Adapter 제작

지금까지 한 것을 정리하면

  • 데이터들이 들어갈 틀(Layout)
  • 들어갈 데이터의 형태(Data Class)
  • 데이터가 틀 안에 들어갈 수 있게 하는 기능 정의(ViewHolder)

이와 같은데, 이제는 ViewHolder에서 정의한 기능을 실제로 사용할 수 있는 구현체를 만들어야 한다. 즉, 데이터를 받아오고 이를 레이아웃에 직접 연결하는 함수를 실행시키는 클래스를 만들어야 한다는 말이다. 이런 역할을 하는 클래스를 Adapter라 한다.

그럼 Adapter에서는 1) ViewHolder에 담길 데이터들(List 형태의 데이터들) 2) 데이터들과 레이아웃을 묶을 수 있게 레이아웃을 inflate할 수 있는 함수 등이 있어야 할 것이다. 이런 기능은 RecyclerView.Adapter 제너릭(구현된 ViewHolder에 맞춰서) 클래스에 존재하기에 우리가 사용할 어댑터는 이를 상속받으면 된다. 다음과 같이 구현해보자

//ViewHolder에 맞춰서 Adapter가 구현된다
class MyAdapter : RecyclerView.Adapter<MyAdapter.MyViewHolder>(){
    //데이터들을 저장하는 변수
    private var data = mutableListOf<LayoutData>()
    
    class MyViewHolder(private val binding: ViewDataBinding) : RecyclerView.ViewHolder(binding,root) {
    //View와 데이터를 연결시키는 함수
        fun bind(layoutData : LayoutData) {
    	    binding.tvGoods.text = layoutData.tv_goods
    	    ...
        }    
    
    //상속받으면 자동 생성
    //ViewHolder에 쓰일 Layout을 inflate하는 함수
    //ViewGroup의 context를 사용하여 특정 화면에서 구현할 수 있도록 함
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): FeatureViewHolder {
        val layoutInflater = LayoutInflater.from(parent.context)
        val binding = DataBindingUtil.inflate(layoutInflater, R.layout.item_product_review, parent, false)
        return MyViewHolder(binding)
    }

    //상속받으면 자동 생성
    override fun getItemCount(): Int = data.size

    //상속받으면 자동 생성
    //ViewHolder에서 데이터 묶는 함수가 실행되는 곳
    override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
        holder.onBind(data[position])
    }
    
    fun replaceList(newList: MutableList<LayoutData>) {
        data = newList.toMutableList()
        //어댑터의 데이터가 변했다는 notify를 날린다
        notifyDataSetChanged()
    }
}

RecyclerView의 어댑터는 위와 같이 구현할 수 있다.

5. 이제 Activity/Fragment에 부착해보자

실제 Activity/Fragment에 부착하려면?

우선 1) 어댑터를 만들고 2) 데이터를 어댑터 안에다 넣어야한다.
그리고 3) 어댑터 안에 데이터가 바뀌었다는 notify를 하고 4) 실제 RecyclerView의 어댑터를 만든 어댑터로 설정한다.

val mDatas = mutableListOf<LayoutData>()

//어댑터를 생성한다
myAdapter = MyAdapter()

//데이터를 생성해야한다(서버통신을 안 할 경우 이렇게)
mDatas.apply(
    add(
    	LayoutData(
        ...
        )
    )
    add(
    	LayoutData(
        ...
        )
    )
    ...
)

//데이터를 어댑터 안에 넣는다
myAdapter.replaceList(mDatas)

//실제 RecyclerView의 adapter를 만든 adapter로 설정한다
binding.rvMyRecyclerView.adapter = myAdapter

위와 같은 설정을 끝내면 RecyclerView의 작동이 정상적으로 잘 될 것이다.
아마 안드로이드를 처음 공부할 때 제일 먼저 마주치는 고비가 RecyclerView일텐데, 이 게시글이 어려움을 벗어나는데 조금이나마 도움이 되었으면 필자는 바라고 있다.

profile
이현우의 개발 브이로그
post-custom-banner

3개의 댓글

comment-user-thumbnail
2020년 11월 7일

ViewHolder로 읽기 좋은 코드를 구현하고 싶어서 구글링 하던 중 보고 들어왔어요. 한 수 배우고 갑니다^^

답글 달기
comment-user-thumbnail
2022년 3월 10일

잘 보고 있습니다! 마지막에 프래그먼트에 부착할땐 저 코드를 프래그먼트 내부에 구현해주면 되는건가요??

1개의 답글