Recycler View란?
리사이클러뷰(RecyclerView)는 많은 리스트 데이터들을 효율적으로 렌더링하기 위해 제공되는 뷰로, 기존에 쓰이던 리스트뷰(ListView)보다 성능과 유연성이 뛰어나 리스트뷰를 대체하고있다. 지금와서 리스트뷰는 안 쓰인다고 보면 된다.
리사이클러뷰가 뭔지 간단하게 설명하자면 많은 수의 데이터를 스크롤 가능한 리스트 형태로 표시해주는 뷰그룹이라고 할 수 있다.
리사이클러뷰를 사용해 데이터를 표시하기 위해서는 3가지 구성 요소를 구현해야한다.
- Adapter: 데이터를 사용해 뷰를 생성하는 역할
- LayoutManager: 아이템들이 나열되는 형태를 관리하는 역할. 이를 사용해 수평, 수직 형태로 표현하는 건 물론 그리드 등의 형태로도 표시가 가능하다.
- ViewHolder: 어댑터를 통해 만들어지는 뷰를 저장하는 객체
구현
만약 아래와 같은 뷰를 구현한다고 생각해보자.

나는 보통 리사이클러뷰를 사용할 때 아래와 같은 단계를 거친다.
- 뷰에 리사이클러뷰 추가
- 아이템뷰 레이아웃 구성
- 리사이클러뷰 어댑터 구현
- 뷰홀더 구현 및 바인딩
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
'Programming > Android_Kotlin' 카테고리의 다른 글
Android Custom View 만들기 (1) | 2021.11.13 |
---|---|
Array의 toList()와 asList() (0) | 2021.10.17 |
RecyclerView 첫 번째, 마지막 아이템 간격 주기 (0) | 2021.10.17 |
Room DB 사용하기 (0) | 2020.09.30 |
Spinner에 데이터 연동하기 (0) | 2020.09.28 |