본문 바로가기

Programming/Android_Kotlin

RecyclerView Adapter 연동하기

Recycler View란?

리사이클러뷰(RecyclerView)는 많은 리스트 데이터들을 효율적으로 렌더링하기 위해 제공되는 뷰로, 기존에 쓰이던 리스트뷰(ListView)보다 성능과 유연성이 뛰어나 리스트뷰를 대체하고있다. 지금와서 리스트뷰는 안 쓰인다고 보면 된다.

리사이클러뷰가 뭔지 간단하게 설명하자면 많은 수의 데이터를 스크롤 가능한 리스트 형태로 표시해주는 뷰그룹이라고 할 수 있다.

리사이클러뷰를 사용해 데이터를 표시하기 위해서는 3가지 구성 요소를 구현해야한다.

 

  1. Adapter: 데이터를 사용해 뷰를 생성하는 역할
  2. LayoutManager: 아이템들이 나열되는 형태를 관리하는 역할. 이를 사용해 수평, 수직 형태로 표현하는 건 물론 그리드 등의 형태로도 표시가 가능하다.
  3. ViewHolder: 어댑터를 통해 만들어지는 뷰를 저장하는 객체

구현

만약 아래와 같은 뷰를 구현한다고 생각해보자.

 

 

나는 보통 리사이클러뷰를 사용할 때 아래와 같은 단계를 거친다.

  1. 뷰에 리사이클러뷰 추가
  2. 아이템뷰 레이아웃 구성
  3. 리사이클러뷰 어댑터 구현
  4. 뷰홀더 구현 및 바인딩

0. 데이터 준비

리사이클러뷰에서 사용할 데이터를 준비해준다. 보통은 서버에서 가져오겠지만 이번 글에서는 그 정도까진 아니고 하드코딩으로 입력한 데이터를 보여줄 것이다. 우선 모델을 구현한다.

 

// ColorModel.kt
data class ColorModel (
    val colorText: String,
    val colorString: String
) {
    val color get() = Color.parseColor(colorString)
}

 

colorText와 colorString을 받고 color를 사용하면 자동으로 Color로 반환해주는 data class를 만들었다. 그리고 RecyclerView는 리스트 데이터를 표시하기 위한 뷰이니 만큼 이 모델을 활용한 리스트를 준비한다.

 

// ColorModel.kt
data class ColorModel(...) {
        companion object {
        val colorList = listOf(
            ColorModel("red", "#FF0000"),
            ColorModel("crimson", "#DC143C"),
            ColorModel("firebrick", "#B22222"),
            ColorModel("maroon", "#800000"),
            ColorModel("darkred", "#8B0000"),
            ColorModel("brown", "#A52A2A"),
            ColorModel("sienna", "#A0522D"),
            ColorModel("saddlebrown", "#8B4513"),
            ColorModel("indianred", "#CD5C5C"),
            ColorModel("rosybrown", "#BC8F8F"),
            ColorModel("lightcoral", "#F08080"),
            ColorModel("salmon", "#FA8072"),
            ColorModel("darksalmon", "#E9967A"),
            ColorModel("coral", "#FF7F50"),
            ColorModel("tomato", "#FF6347"),
            ColorModel("sandybrown", "#F4A460"),
            ColorModel("lightsalmon", "#FFA07A"),
            ColorModel("peru", "#CD853F"),
            ColorModel("chocolate", "#D2691E"),
            ColorModel("orangered", "#FF4500"),
            ColorModel("orange", "#FFA500"),
            ColorModel("darkorange", "#FF8C00"),
            ColorModel("tan", "#D2B48C"),
            ColorModel("peachpuff", "#FFDAB9"),
            ColorModel("bisque", "#FFE4C4"),
            ColorModel("moccasin", "#FFE4B5"),
            ColorModel("navajowhite", "#FFDEAD"),
            ColorModel("wheat", "#F5DEB3"),
            ColorModel("burlywood", "#DEB887"),
            ColorModel("darkgoldenrod", "#B8860B"),
            ColorModel("goldenrod", "#DAA520"),
            ColorModel("gold", "#FFD700"),
            ColorModel("yellow", "#FFFF00"),
            ColorModel("lightgoldenrodyellow", "#FAFAD2"),
            ColorModel("palegoldenrod", "#EEE8AA"),
            ColorModel("khaki", "#F0E68C"),
            ColorModel("darkkhaki", "#BDB76B"),
            ColorModel("lawngreen", "#7CFC00"),
            ColorModel("greenyellow", "#ADFF2F"),
            ColorModel("chartreuse", "#7FFF00"),
            ColorModel("lime", "#00FF00"),
            ColorModel("limegreen", "#32CD32"),
            ColorModel("yellowgreen", "#9ACD32"),
            ColorModel("olive", "#808000"),
            ColorModel("olivedrab", "#6B8E23"),
            ColorModel("darkolivegreen", "#556B2F"),
            ColorModel("forestgreen", "#228B22"),
            ColorModel("darkgreen", "#006400"),
            ColorModel("green", "#008000"),
            ColorModel("seagreen", "#2E8B57"),
            ColorModel("mediumseagreen", "#3CB371"),
            ColorModel("darkseagreen", "#8FBC8F"),
            ColorModel("lightgreen", "#90EE90"),
            ColorModel("palegreen", "#98FB98"),
            ColorModel("springgreen", "#00FF7F"),
            ColorModel("mediumspringgreen", "#00FA9A"),
            ColorModel("teal", "#008080"),
            ColorModel("darkcyan", "#008B8B"),
            ColorModel("lightseagreen", "#20B2AA"),
            ColorModel("mediumaquamarine", "#66CDAA"),
            ColorModel("cadetblue", "#5F9EA0"),
            ColorModel("steelblue", "#4682B4"),
            ColorModel("aquamarine", "#7FFFD4"),
            ColorModel("powderblue", "#B0E0E6"),
            ColorModel("paleturquoise", "#AFEEEE"),
            ColorModel("lightblue", "#ADD8E6"),
            ColorModel("lightsteelblue", "#B0C4DE"),
            ColorModel("skyblue", "#87CEEB"),
            ColorModel("lightskyblue", "#87CEFA"),
            ColorModel("mediumturquoise", "#48D1CC"),
            ColorModel("turquoise", "#40E0D0"),
            ColorModel("darkturquoise", "#00CED1"),
            ColorModel("aqua", "#00FFFF"),
            ColorModel("cyan", "#00FFFF"),
            ColorModel("deepskyblue", "#00BFFF"),
            ColorModel("dodgerblue", "#1E90FF"),
            ColorModel("cornflowerblue", "#6495ED"),
            ColorModel("royalblue", "#4169E1"),
            ColorModel("blue", "#0000FF"),
            ColorModel("mediumblue", "#0000CD"),
            ColorModel("navy", "#000080"),
            ColorModel("darkblue", "#00008B"),
            ColorModel("midnightblue", "#191970"),
            ColorModel("darkslateblue", "#483D8B"),
            ColorModel("slateblue", "#6A5ACD"),
            ColorModel("mediumslateblue", "#7B68EE"),
            ColorModel("mediumpurple", "#9370DB"),
            ColorModel("darkorchid", "#9932CC"),
            ColorModel("darkviolet", "#9400D3"),
            ColorModel("blueviolet", "#8A2BE2"),
            ColorModel("mediumorchid", "#BA55D3"),
            ColorModel("plum", "#DDA0DD"),
            ColorModel("lavender", "#E6E6FA"),
            ColorModel("thistle", "#D8BFD8"),
            ColorModel("orchid", "#DA70D6"),
            ColorModel("violet", "#EE82EE"),
            ColorModel("indigo", "#4B0082"),
            ColorModel("darkmagenta", "#8B008B"),
            ColorModel("purple", "#800080"),
            ColorModel("mediumvioletred", "#C71585"),
            ColorModel("deeppink", "#FF1493"),
            ColorModel("fuchsia", "#FF00FF"),
            ColorModel("magenta", "#FF00FF"),
            ColorModel("hotpink", "#FF69B4"),
            ColorModel("palevioletred", "#DB7093"),
            ColorModel("lightpink", "#FFB6C1"),
            ColorModel("pink", "#FFC0CB"),
            ColorModel("mistyrose", "#FFE4E1"),
            ColorModel("blanchedalmond", "#FFEBCD"),
            ColorModel("lightyellow", "#FFFFE0"),
            ColorModel("cornsilk", "#FFF8DC"),
            ColorModel("antiquewhite", "#FAEBD7"),
            ColorModel("papayawhip", "#FFEFD5"),
            ColorModel("lemonchiffon", "#FFFACD"),
            ColorModel("beige", "#F5F5DC"),
            ColorModel("linen", "#FAF0E6"),
            ColorModel("oldlace", "#FDF5E6"),
            ColorModel("lightcyan", "#E0FFFF"),
            ColorModel("aliceblue", "#F0F8FF"),
            ColorModel("whitesmoke", "#F5F5F5"),
            ColorModel("lavenderblush", "#FFF0F5"),
            ColorModel("floralwhite", "#FFFAF0"),
            ColorModel("mintcream", "#F5FFFA"),
            ColorModel("ghostwhite", "#F8F8FF"),
            ColorModel("honeydew", "#F0FFF0"),
            ColorModel("seashell", "#FFF5EE"),
            ColorModel("ivory", "#FFFFF0"),
            ColorModel("azure", "#F0FFFF"),
            ColorModel("snow", "#FFFAFA"),
            ColorModel("white", "#FFFFFF"),
            ColorModel("gainsboro", "#DCDCDC"),
            ColorModel("lightgrey", "#D3D3D3"),
            ColorModel("silver", "#C0C0C0"),
            ColorModel("darkgray", "#A9A9A9"),
            ColorModel("lightslategray", "#778899"),
            ColorModel("slategray", "#708090"),
            ColorModel("gray", "#808080"),
            ColorModel("dimgray", "#696969"),
            ColorModel("darkslategray", "#2F4F4F"),
            ColorModel("black", "#000000")
        )
    }
        ...
}

 

불러오기 쉽도록 ColorModel의 CompanionObject로 추가했다.

1. 뷰에 리사이클러뷰 추가

이제 본격적으로 리사이클러뷰를 구현하기 위해 우선 리사이클러뷰를 넣을 layout에 RecyclerView를 추가한다.

 

<!-- activity_main.xml -->
...
<androidx.recyclerview.widget.RecyclerView
        android:id="@+id/rv_color"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"/>
...

 

여기서 layoutManager는 xml에서 지정해줘도 되고 코드에서 구현해줄 수도 있다.

나는 자꾸 까먹기도 하고 정확한 프리뷰를 위해 xml에서 지정해주는 편이다.

(자꾸 RecyclerView에 데이터가 안 떠서 확인해보면 대부분 layoutManager를 지정해주지 않아서 발생하는 오류인 경우가 많았다. 앱이 터지거나 하는 게 아니라 로그 한 줄만 나오는 오류라 발견하기가 꽤 어려운 편)

2. 아이템뷰 레이아웃 구성

 

 

리사이클러뷰를 추가했다면 리사이클러뷰에 들어갈 아이템뷰를 구현한다. 간단하게 ImageView와 TextView로 구성한 색깔 라벨을 만들었다.

 

<!-- item_color.xml -->
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="horizontal">
    <ImageView
        android:id="@+id/iv_color"
        android:layout_width="40dp"
        android:layout_height="40dp"
        android:src="@color/black"/>

    <TextView
        android:id="@+id/tv_color"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:layout_margin="10dp"
        android:text="black" />
</LinearLayout>

 

그리고 명확한 프리뷰를 위해 리사이클러뷰로 돌아가 listitem을 추가해준다.

 

<!-- activity_main.xml -->
...
<androidx.recyclerview.widget.RecyclerView
        android:id="@+id/rv_color"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
        tools:listitem="@layout/item_color"/>
...

 

 

그러면 이렇게 프리뷰에도 아이템이 표시되는 것을 확인할 수 있다.

3. 리사이클러뷰 어댑터 구현

이제 본격적으로 어댑터를 구현해야한다. 어댑터는 RecyclerView.Adapter를 상속받아 구현할 수 있다.

ColorAdapter Class를 만들어주자

 

// ColorAdapter.kt
...
class ColorAdapter(private val colorList: List<ColorModel>) : RecyclerView.Adapter<ColorAdapter.ColorViewHolder>() {

}
...

 

나는 보통 Adapter의 인자로 외부에서 데이터 리스트를 받도록 한다.

RecyclerView.Adapter를 상속받을 때 제너릭에 ViewHolder Class를 지정해줘야하는데 대개 Adapter의 이름을 그대로 써준다.

MusicAdapter라면 MusicViewHolder로 써주는 식인데 나는 여기서 Adapter의 inner class로 ViewHolder를 구현할 것이기 때문에 ColorAdapter.ColorViewHolder로 넣어줬다.

4. 뷰홀더 구현 및 바인딩

4-1. 뷰홀더 구현

다음은 이제 뷰를 실제로 구현하고 뷰와 어댑터를 연결하는 작업을 진행해야 한다.

우선 ColorAdapter 내에 inner class로 ColorViewHolder를 구현한다.

 

// ColorAdapter.kt
...
class ColorAdapter(private val colorList: List<ColorModel>) : RecyclerView.Adapter<ColorAdapter.ColorViewHolder>() {
        inner class ColorViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
        fun bind(color: ColorModel) {
            itemView.findViewById<ImageView>(R.id.iv_color).setColorFilter(color.color)
            itemView.findViewById<TextView>(R.id.tv_color).text = color.colorText

            itemView.setOnClickListener {
                Toast.makeText(itemView.context, "It's so ${color.colorText}", Toast.LENGTH_SHORT).show()
            }
        }
    }
}
...

 

ViewHolder 내에는 bind 함수를 구현해야한다. 이를 Adapter에서 호출해 데이터를 View에 뿌려주는 역할을 한다.

bind 함수는 데이터가 되는 모델을 인자로 받아 가공해 뷰로 표현해주도록 구현해주면 된다.

4-2. 바인딩

 

ViewHolder가 준비되었다면 Adapter와 ViewHolder를 연결시켜줘야한다. 이를 위해서 Adapter의 3가지 함수를 override해야한다.

 

메서드명 내용 추가 설명
getItemCount(): Int 전체 아이템 갯수를 반환한다.  
onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder viewType 형태의 아이템 뷰를 위한 뷰 홀더 객체 생성해준다. getItemViewType(position: Int): Int 메서드를 override하면 인자로 들어오는 viewType을 제어할 수 있다.
onBindViewHolder(holder: ViewHolder, position: Int) ViewHolder와 데이터를 연결할 때 호출된다.  
// ColorAdapter.kt
class ColorAdapter(private val colorList: List<ColorModel>) : RecyclerView.Adapter<ColorAdapter.ColorViewHolder>() {
    ...
        // 데이터의 크기를 반환한다.
        // 인자로 받은 데이터의 크기를 반환하도록 구현했다.
        override fun getItemCount(): Int = colorList.size

        // ViewHolder를 생성해 반환한다.
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ColorViewHolder {
        return ColorViewHolder(
                        // 새로운 뷰를 생성해 뷰홀더에 인자로 넣어준다.
            LayoutInflater.from(parent.context).inflate(R.layout.item_color, parent, false)
        )
    }

        // 반환된 ViewHolder에 데이터를 연결한다.
    override fun onBindViewHolder(holder: ColorViewHolder, position: Int) {
        holder.bind(colorList[position])
    }
        ...
}

 

이렇게 어댑터를 구현했다면 RecyclerView에 어댑터를 붙여줘야한다.

 

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        findViewById<RecyclerView>(R.id.rv_color).adapter = ColorAdapter(ColorModel.colorList)
    }
}

 

RecyclerView의 adapter에 직접 구현한 Adapter Class를 넣어주면 정상적으로 작동함을 볼 수 있다.

(만약 xml에서 layoutManager를 지정해주지 않았다면 해당 과정에서 LayoutManager를 별도로 넣어주어야한다.)

 

 

실행해보면 원하는대로 구현된 것을 확인할 수 있다.


참고자료

https://developer.android.com/guide/topics/ui/layout/recyclerview?hl=ko

https://recipes4dev.tistory.com/154