본문 바로가기

이상/Andrioid

[Android/Kotlin] 리스트 만들기 (with RecyclerView)

반응형

리스트는 앱에서 가장 필수적으로 필요한 기능이다.

 

6년 전, 처음 안드로이드 앱을 만들었을 때는

 

TableLayout을 이용해서 리스트를 만들었다.

 

이번에 다시 찾아보니 RecyclerView라는게 있었고

 

둘의 차이는 간단하게

 

TableLayout은 고정된 row와 column을 이용하여 리스트를 만들고

 

RecyclerView는 Adapter(와 ViewHolder)를 이용하여 리스트를 만든다.

 

RecyclerView는 더욱 발전된 형태로

 

dynamic하게 item을 쉽게 추가하고 삭제할 수 있고

 

내부 item을 자유롭게 만들 수 있기 때문에

 

TableLayout보다는 RecyclerView를 많이 쓰이는 것 같다.

 

Adapter와 ViewHolder를 이용하여 RecyclerView를 만들어 본다.

 

 

 

1. 구조 파악

 

Activity에 FrameLayout을 넣어 RecyclerView로 쓰일 Fragment의 영역을 잡는다.

Activity[FrameLayout - Fragment]

 

FrameLayout에 Fragment를 붙이고 Fragment에 RecyclerView를 넣는다.

Activity[FrameLayout - Fragment[RecyclerView]]

 

Adapter를 만들어 RecyclerView의 adapter로 연결하여 Item을 배치한다.

Activity[FrameLayout - Fragment[RecyclerView - Adapter[Item]]]

 

RecyclerView 구조

 

리스트에 보여줄 데이터는 Activity에서 Fragment로 넘겨 RecyclerView에 보여준다.

 

이 순서대로 만들어보자.

 

 

 

2. Activity

 

프로젝트를 생성하고 Activity에 FrameLayout으로 RecyclerView의 영역을 잡는다.

 

FrameLayout의 height를 match_parent로 해도 되지만 확장성을 위해 wrap_content로 지정하고

 

Activity의 아래쪽으로 붙인다.

 

activity_main.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?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">
 
    <FrameLayout
        android:id="@+id/listFrame"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintBottom_toBottomOf="parent">
    </FrameLayout>
 
</androidx.constraintlayout.widget.ConstraintLayout>
cs

 

 

RecyclerView에 보여줄 데이터를 만든다.

 

MainActivity.kt

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
class TestData(
    private var data1: String= null,
    private var data2: String= null,
    private var data3: String= null
){
    fun getData1(): String? {
        return data1
    }
    fun setData1(name: String) {
        this.data1 = data1
    }
    fun getData2(): String? {
        return data2
    }
    fun setData2(address: String) {
        this.data2 = data2
    }
    fun getData3(): String? {
        return data3
    }
    fun setData3(type: String) {
        this.data3 = data3
    }
}
 
class MainActivity : AppCompatActivity() {
 
    var dataList: ArrayList<TestData> = arrayListOf(
        TestData("첫 번째 데이터1""두 번째 데이터1""세 번째 데이터1"),
        TestData("첫 번째 데이터2""두 번째 데이터2""세 번째 데이터2"),
        TestData("첫 번째 데이터3""두 번째 데이터3""세 번째 데이터3"),
        TestData("첫 번째 데이터4""두 번째 데이터4""세 번째 데이터4"),
        TestData("첫 번째 데이터5""두 번째 데이터5""세 번째 데이터5"),
        TestData("첫 번째 데이터6""두 번째 데이터6""세 번째 데이터6"),
        TestData("첫 번째 데이터7""두 번째 데이터7""세 번째 데이터7"),
        TestData("첫 번째 데이터8""두 번째 데이터8""세 번째 데이터8"),
        TestData("첫 번째 데이터9""두 번째 데이터9""세 번째 데이터9"),
        TestData("첫 번째 데이터10""두 번째 데이터10""세 번째 데이터10"),
        TestData("첫 번째 데이터11""두 번째 데이터11""세 번째 데이터11"),
        TestData("첫 번째 데이터12""두 번째 데이터12""세 번째 데이터12"),
        TestData("첫 번째 데이터13""두 번째 데이터13""세 번째 데이터13"),
        TestData("첫 번째 데이터14""두 번째 데이터14""세 번째 데이터14"),
        TestData("첫 번째 데이터15""두 번째 데이터15""세 번째 데이터15")
    )
 
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
    }
 
}
cs

 

 

 

3. Fragment

 

프로젝트에 Fragment를 추가하고 RecyclerView를 추가한다.

 

RecyclerView의 height만 wrap_content로 지정한다.

 

fragment_first.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout 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=".FirstFragment">
 
    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">
 
        <androidx.recyclerview.widget.RecyclerView
            android:id="@+id/listView"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            app:layout_constraintTop_toTopOf="parent">
 
        </androidx.recyclerview.widget.RecyclerView>
 
    </androidx.constraintlayout.widget.ConstraintLayout>
 
</FrameLayout>
cs

 

FirstFragment.kt에서 필요없는 부분을 다 없앤다.

 

FirstFragment.kt

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class FirstFragment : Fragment() {
 
    override fun onCreateView(
            inflater: LayoutInflater, container: ViewGroup?,
            savedInstanceState: Bundle?
    ): View? {
        // Inflate the layout for this fragment
        return inflater.inflate(R.layout.fragment_first, container, false)
    }
 
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
    }
 
}
cs

 

Fragment를 만들었으니 Activity에서 Fragment를 띄워줘야 한다.

 

띄워주면서 만들었던 리스트 데이터를 intent를 이용하여 넘긴다.

 

Activity.kt

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
class MainActivity : AppCompatActivity() {
 
    var dataList: ArrayList<TestData> = arrayListOf(
        TestData("첫 번째 데이터1""두 번째 데이터1""세 번째 데이터1"),
        TestData("첫 번째 데이터2""두 번째 데이터2""세 번째 데이터2"),
        TestData("첫 번째 데이터3""두 번째 데이터3""세 번째 데이터3"),
        TestData("첫 번째 데이터4""두 번째 데이터4""세 번째 데이터4"),
        TestData("첫 번째 데이터5""두 번째 데이터5""세 번째 데이터5"),
        TestData("첫 번째 데이터6""두 번째 데이터6""세 번째 데이터6"),
        TestData("첫 번째 데이터7""두 번째 데이터7""세 번째 데이터7"),
        TestData("첫 번째 데이터8""두 번째 데이터8""세 번째 데이터8"),
        TestData("첫 번째 데이터9""두 번째 데이터9""세 번째 데이터9"),
        TestData("첫 번째 데이터10""두 번째 데이터10""세 번째 데이터10"),
        TestData("첫 번째 데이터11""두 번째 데이터11""세 번째 데이터11"),
        TestData("첫 번째 데이터12""두 번째 데이터12""세 번째 데이터12"),
        TestData("첫 번째 데이터13""두 번째 데이터13""세 번째 데이터13"),
        TestData("첫 번째 데이터14""두 번째 데이터14""세 번째 데이터14"),
        TestData("첫 번째 데이터15""두 번째 데이터15""세 번째 데이터15")
    )
 
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
 
        val transaction = supportFragmentManager.beginTransaction()
        transaction.replace(
            R.id.listFrame,
            FirstFragment()
        )
        transaction.commit()
        intent.putExtra("DataList", dataList)
    }
 
}
cs

 

Fragment에 Activity에서 intent로 넘긴 데이터를 받는 부분을 추가한다.

 

Fragment.kt

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class FirstFragment : Fragment() {
 
    override fun onCreateView(
            inflater: LayoutInflater, container: ViewGroup?,
            savedInstanceState: Bundle?
    ): View? {
        // Inflate the layout for this fragment
        return inflater.inflate(R.layout.fragment_first, container, false)
    }
 
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
 
        var list: ArrayList<TestData> = requireActivity().intent!!.extras!!.get("DataList") as ArrayList<TestData>
        Log.e("FirstFragment""Data List: ${list}")
    }
}
cs

 

 

 

4. Adapter

 

Adapter의 역할

 

내가 이해한 Adapter의 역할이다.

 

RecyclerView는 하나의 셀 item을 여러개 보여주는 형태이므로

 

item이 될 xml을 만들어 거기에 데이터를 넣고 RecyclerView에 배치한다.

 

데이터를 담을 item을 만들어보자.

 

item_data_list.xml

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
<?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"
    android:id="@+id/storeItemLayout"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:background="@drawable/item_background">
 
    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:gravity="center_vertical"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        android:layout_marginTop="4dp"
        android:layout_marginBottom="4dp"
        android:layout_marginStart="8dp">
 
        <TextView
            android:id="@+id/data1Text"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:textSize="16sp"
            android:textColor="#000000"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintBottom_toTopOf="@id/data2Text"/>
 
        <TextView
            android:id="@+id/data2Text"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:textSize="12sp"
            app:layout_constraintTop_toBottomOf="@id/data2Text"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintBottom_toTopOf="@id/data3Text"/>
 
        <TextView
            android:id="@+id/data3Text"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:textSize="12sp"
            app:layout_constraintTop_toBottomOf="@id/data2Text"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintBottom_toBottomOf="parent"/>
 
    </androidx.constraintlayout.widget.ConstraintLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
cs

 

이제 Adapter를 만들어보자.

 

ListAdapter.kt

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
// 리스트 데이터를 넘겨받아야 한다.
class ListAdapter (private var list: MutableList<TestData>): RecyclerView.Adapter<ListAdapter.ListItemViewHolder> () {
 
    // inner class로 ViewHolder 정의
    inner class ListItemViewHolder(itemView: View?): RecyclerView.ViewHolder(itemView!!) {
 
        var data1Text: TextView = itemView!!.findViewById(R.id.data1Text)
        var data2Text: TextView = itemView!!.findViewById(R.id.data2Text)
        var data3Text: TextView = itemView!!.findViewById(R.id.data3Text)
 
        // onBindViewHolder의 역할을 대신한다.
        fun bind(data: TestData, position: Int) {
            Log.d("ListAdapter""===== ===== ===== ===== bind ===== ===== ===== =====")
            Log.d("ListAdapter", data.getData1()+" "+data.getData2()+" "+data.getData3())
            data1Text.text = data.getData1()
            data2Text.text = data.getData2()
            data3Text.text = data.getData3()
        }
    }
 
    // ViewHolder에게 item을 보여줄 View로 쓰일 item_data_list.xml를 넘기면서 ViewHolder 생성
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ListItemViewHolder {
        val view = LayoutInflater.from(parent.context).inflate(R.layout.item_data_list, parent, false)
        return ListItemViewHolder(view)
    }
 
    override fun getItemCount(): Int {
        return list.count()
    }
 
    // ViewHolder의 bind 메소드를 호출한다.
    override fun onBindViewHolder(holder: ListAdapter.ListItemViewHolder, position: Int) {
        Log.d("ListAdapter""===== ===== ===== ===== onBindViewHolder ===== ===== ===== =====")
        holder.bind(list[position], position)
    }
 
}
cs

 

inner class로 ViewHolder로 만들고 onBindViewHolder에서 ViewHolder의 bind 메소드로 데이터를 넘기므로

 

ViewHolder의 bind는 Adapter의 onBindViewHolder()의 역할을 대신 하게 된다.

 

onBindViewHolder()는 RecyclerView의 몇번째 셀에 어떤 데이터를 넣어야 할지에 대해 관리하며

 

그 역할을 ViewHolder의 bind()에서 대신 하게 된다.

 

 

 

Fragment에서 RecyclerView의 adapter로 ListAdapter를 지정한다.

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
class FirstFragment : Fragment() {
    
    // RecyclerView.adapter에 지정할 Adapter
    private lateinit var listAdapter: ListAdapter
 
    override fun onCreateView(
            inflater: LayoutInflater, container: ViewGroup?,
            savedInstanceState: Bundle?
    ): View? {
        // Inflate the layout for this fragment
        return inflater.inflate(R.layout.fragment_first, container, false)
    }
 
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
 
        var list: ArrayList<TestData> = requireActivity().intent!!.extras!!.get("DataList") as ArrayList<TestData>
        Log.e("FirstFragment""Data List: ${list}")
 
        // Fragment에서 전달받은 list를 넘기면서 ListAdapter 생성
        listAdapter = ListAdapter(list)
        listView.layoutManager = LinearLayoutManager(activity, RecyclerView.VERTICAL, false)
        // RecyclerView.adapter에 지정
        listView.adapter = listAdapter
    }
}
cs

 

 

앱을 실행해보자.

 

RecyclerView 실행

 

끝.

반응형