Kotlin Recycler View 구현하기
RecyclerView
RecyclerView는 데이터 목록을 제한된 영역에서 다양한 포맷으로 화면에 출력하는 위젯이다.
사용하기 위해서 RecyclerView, 데이터를 관리 및 목록 표시를 담당하는 Adapter, Adapter를 통해 RecyclerView에서 각 아이템의 View를 제공하는 ViewHolder, 총 3가지가 필요하다.
- RecyclerView 기본 구현
- RecyclerView 다양한 방식의 Layout
- RecyclerView 선택 커스터마이징 (다른 포스트)
1. RecyclerView 기본 구현
ListView처럼 LinearLayout 형식으로 간단히 구현해 볼 것이다.
기본 구현 순서
- RecyclerView를 추가
- 리스트의 아이템으로 들어갈 Layout 디자인
- 리스트의 Model Class 생성
- ViewHolder 생성
- Adapter 생성
- ListView를 구현할 Activity에서 Data List - Adapter - ListView를 연결
1) RecyclerView를 추가
activity_main.xml에 RecyclerView 1개만 추가했다. RecyclerView는 기본 라이브러리가 아니므로 추가할 것이냐는 창이 뜬다. ok를 눌러주면 추가된다.
2) 리스트의 아이템으로 들어갈 Layout 디자인
Layout Resource File을 만들어 리스트의 각 아이템으로 들어갈 Layout을 디자인한다. ConstraintLayout에 간단하게 사진과 이름, 나이가 들어갈 TextView 2개를 선언했다.
3) 리스트의 Model Class 생성
리스트 데이터를 저장하고자 Custom Class로 Model을 만들었다.
1
|
class Person (val name : String, val age : Int)
|
4) ViewHolder Class 생성
RecyclerView.ViewHolder와 LayoutContainer 인터페이스를 상속받아 View 멤버를 override하고 View?의 ?를 제거하여 null을 허용하지 않도록 한다. override한 View는 RecyclerView.ViewHolder의 생성자로 넘겨준다. containerView에는 아까 디자인한 각 item 레이아웃 객체를 받을 것이다.
LayoutContainer는 kotlin android extensin 패키지에서 제공한다. 이를 상속받으면 내부적으로 View를 캐시하는 코드가 생성되고 더 빠른 화면 출력이 가능하다.
https://proandroiddev.com/kotlin-android-extensions-using-view-binding-the-right-way-707cd0c9e648
1
|
class DataViewHolder(override val containerView: View)
: RecyclerView.ViewHolder(containerView), LayoutContainer
|
5) Adapter 생성
생성자에서는 데이터 목록으로 쓰일 List를 받는다. RecyclerView.Adapter를 상속받고 ViewHolder로 쓰일 곳에 아까 생성한 Class를 Generic으로 넘겨준다. 이제 상속받은 클래스로부터 추상 메서드들을 override하면 3가지 메서드가 생성된다.
- onCreateViewHolder() : RecyclerView가 필요할 때마다 호출하여 ViewHolder를 생성
- getItemCount() : 생성자의 List의 아이템의 개수 반환
- onBindViewHolder() : 생성된 ViewHolder가 화면에 표시될 데이터를 바인딩함
onCreateViewHolder()
- 아까 만든 아이템 레이아웃을 사용하려면 LayoutInflater Class를 통해 xml 파일을 View 객체 형태로 생성해야 함
- 생성한 View 객체를 ViewHolder에 넘겨 반환한다.
getItemCount()
- 생성자의 list의 아이템 개수인 list.count()를 반환한다.
onBindViewHolder()
- ViewHolder의 멤버인 containerView로 View의 Id를 참조하여 설정한다.
- holder.containerView.(ViewId)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
class DataAdapter (val list : List<Person>) : RecyclerView.Adapter<DataViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): DataViewHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.item_list_1, parent, false)
return DataViewHolder(view)
}
override fun getItemCount(): Int {
return list.count()
}
override fun onBindViewHolder(holder: DataViewHolder, position: Int) {
holder.containerView.imageView.setImageResource(R.drawable.ic_launcher_background)
holder.containerView.textName.text = list[position].name
holder.containerView.textAge.text = list[position].age.toString()
}
}
|
6) ListView를 구현할 Activity에서 Data List - Adapter - ListView를 연결
listOf()를 통해 List를 구성하고 Adapter와 연결한다. ListView를 Adapter와 연결하고 LinearLayout으로 나열하기 위해 layoutManager를 LinearLayoutManager를 생성한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val personList = listOf(
Person("a", 1),
Person("b", 2),
Person("c", 3),
Person("d", 4),
Person("e", 5)
)
val adapter = DataAdapter(personList)
personListview.adapter = adapter
personListview.layoutManager = LinearLayout(this)
}
}
|
이처럼 LinearLayout 형식으로 RecyclerView가 생성되었다.
2. 다양한 방식의 Layout
기존에는 LinearLayoutManager를 통해 세로로 일련의 리스트만 표시했다. RecyclerView는 다양한 LayoutManager를 통해 여러 포맷의 Layout 표현이 가능하다.
- LinearLayoutManager : 가로 혹은 세로로 일련의 리스트를 나열
- GridLayoutManager : 바둑판 배열처럼 Grid 형태로 리스트를 나열
- StaggeredLayoutManager : 잡지의 목록처럼 다양한 크기의 Grid 형태의 리스트 나열
layout : width=200dp, height=match_parent
image : width=150dp, height=150dp
layout : width=match_parent, height=wrap_content
image : width=match_parent, height=wrap_content, adjustViewbounds=true
Fruit Class
레이아웃을 디자인하고 People Class를 Fruit Class로 설계하였다.
imageView의 Id를 받기 위해 생성자에 변수를 넣었다.
1
|
class Fruit (val name : String, val imgId : Int)
|
DataAdapter Class
생성자에 list와 다양한 Layout의 Id를 받기 위해 layoutId를 넣었다. onBindViewHolder()에서는 list의 생성자로 들어온 image Id 값을 ImageView의 Resource로 설정했다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
class DataAdapter (val list : List<Fruit>, val layoutId : Int)
: RecyclerView.Adapter<DataViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): DataViewHolder {
val view = LayoutInflater.from(parent.context)
.inflate(layoutId, parent, false)
return DataViewHolder(view)
}
override fun getItemCount(): Int {
return list.count()
}
override fun onBindViewHolder(holder: DataViewHolder, position: Int) {
holder.containerView.imageView.setImageResource(list[position].imgId)
holder.containerView.textName.text = list[position].name
}
}
|
MainActivity
fruitList로 과일 정보를 저장할 리스트를 생성했다.(수박이 중복되는 이유는 스크롤된 이미지까지 나타내기 위해) 아까 생성한 Layout 타입에 따라 Adapter도 여러 개 생성했다. 버튼을 눌러 Linear, Grid, StaggeredGrid을 다른 Adapter로 변환하면서 표시하도록 했다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
|
class MainActivity : AppCompatActivity() {
val context : Context = this
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val fruitList = listOf(
Fruit("사과", R.drawable.img_apple),
Fruit("바나나", R.drawable.img_banana),
Fruit("체리", R.drawable.img_cherry),
Fruit("복숭아", R.drawable.img_peach),
Fruit("파인애플", R.drawable.img_pineapple),
Fruit("수박", R.drawable.img_watermelon),
Fruit("수박", R.drawable.img_watermelon),
Fruit("수박", R.drawable.img_watermelon),
Fruit("수박", R.drawable.img_watermelon),
Fruit("수박", R.drawable.img_watermelon)
)
val adapter1 = DataAdapter(fruitList, R.layout.item_list_1)
val adapter2 = DataAdapter(fruitList, R.layout.item_list_2)
val adapter3 = DataAdapter(fruitList, R.layout.item_list_3)
fruitListview.layoutManager = LinearLayoutManager(this)
fruitListview.adapter = adapter1
LinearButton.setOnClickListener(object : View.OnClickListener {
override fun onClick(v: View?) {
fruitListview.layoutManager = LinearLayoutManager(context)
fruitListview.adapter = adapter1
}
})
GridButton.setOnClickListener(object : View.OnClickListener {
override fun onClick(v: View?) {
fruitListview.layoutManager = GridLayoutManager(context, 2)
fruitListview.adapter = adapter2
}
})
StaggeredGridButton.setOnClickListener(object : View.OnClickListener {
override fun onClick(v: View?) {
fruitListview.layoutManager =
StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.VERTICAL)
fruitListview.adapter = adapter3
}
})
}
|
결과
StaggeredGridLayout에서 스크롤 사용 시 아래 사진이 짤리는 현상
ScrollView로 감싸고 width, height를 match_parent로 설정했다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
|
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<Button
android:id="@+id/StaggeredGridButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="32dp"
android:layout_marginTop="16dp"
android:text="Staggered"
app:layout_constraintStart_toEndOf="@+id/GridButton"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/GridButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="32dp"
android:layout_marginTop="16dp"
android:text="Grid"
app:layout_constraintStart_toEndOf="@+id/LinearButton"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/LinearButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:text="Linear"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<ScrollView
android:layout_marginTop="80dp"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/fruitListview"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/LinearButton" />
</ScrollView>
</androidx.constraintlayout.widget.ConstraintLayout>
|