Android

[Android/Kotlin] Jetpack 리사이클러 뷰(RecyclerView)로 목록 화면 구현하기

haehyun 2022. 1. 8. 17:52
728x90

RecyclerView 

Jetpack(androidx 라이브러리)의 구성요소 중 하나로, 여러 가지 항목을 나열하는 목록화면을 만들 때 사용한다.

대용량 데이터를 화면에 리스트 형태로 출력할 때, 데이터셋의 각 데이터는 하나의 뷰로 표현된다. 리스트의 항목이 여러 개일 경우 화면을 넘어가는 정보는 스크롤로 내려서 확인할 수 있는데, RecyclerView에서는 스크롤을 내림으로써 화면밖으로 사라진 뷰를 제거하지 않고 새롭게 나타난 뷰로 재활용한다. (=말그대로 View를 Recycle하는 방식으로 동작함) 앱에 표시할 데이터가 총 100개이고, 한 화면에 표시할 수 있는 데이터는 10개가 한계일 경우 굳이 100개의 View를 만들지 않고 10~12개 정도의 실제 화면에 표시된 View들만을 만들어 둔 뒤 화면이 변경될 때마다(스크롤 이동) 방금 화면 밖으로 사라진 View를 새롭게 나타난 View로 활용할 수 있다.

 

RecyclerView 구성요소

  • RecyclerView : 데이터에 해당하는 뷰가 포함된 ViewGroup. (androidx.recyclerview.widget.RecyclerView)
  • ViewHolder : 목록의 개별 항목, 각 항목을 구성하는 뷰 객체를 가진다. (RecyclerView.ViewHolder)
  • Adapter : 항목 구성, ViewHolder에 있는 뷰 객체에 데이터를 연결(binding)한다. (RecyclerView.Adapter)
  • LayoutManager : 항목 배치 (기본 제공 LayoutManager 사용 or 직접 정의)
  • ItemDecoration : 항목 디자인 요소 추가

 

RecyclerView 구현 단계

*필요한 작업 파일

 

1. gradle파일 dependencies 항목 선언 [build.gradle (Module:프로젝트명.모듈명)]

dependencies {
    implementation 'androidx.recyclerview:recyclerview:1.1.0'
    // 생략
}

 

2. [res → values → themes] 파일을 수정해 테마를 NoActionBar로 설정 [themes.xml]

<resources xmlns:tools="http://schemas.android.com/tools">
    <style name="Theme.AndroidLab" parent="Theme.MaterialComponents.DayNight.NoActionBar">
        <!-- 생략 -->
    </style>
</resources>

 

3. RecyclerView 레이아웃 XML 파일 작성 [activity_recyclerview.xml]

<?xml version="1.0" encoding="utf-8"?>
<androidx.recyclerview.widget.RecyclerView
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:id="@+id/recyclerView"
    android:layout_height="match_parent"/>

레이아웃 미리보기

 

4. RecyclerView에 표시할 항목을 디자인한 XML 파일 생성 [item_main.xml]

여기서 선언된 <TextView>레이아웃 요소가 ViewHolder에 포함되는 뷰 객체. 즉, 데이터가 바인딩 되는 대상이다.

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/item_root"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:orientation="horizontal"
    android:padding="16dp">

    <TextView
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:id="@+id/item_data"
        android:layout_weight="1"
        android:textStyle="bold"
        android:textSize="16dp" />

</LinearLayout>

 

5. RecyclerView.ViewHolder를 상속하는 ViewHolder 정의 [MainActivity.kt]

*원래 따로 코틀린 파일(Fragement)을 분리해야 하지만, 실습상 편의를 위해 MainActivity.kt 파일에 모아서 작성함

class MyViewHolder(val binding: ItemMainBinding) : RecyclerView.ViewHolder(binding.root)

 

6. RecyclerView.Adapter를 상속하는 Adapter 정의, Adapter 메서드를 사용해서 ViewHolder가 가지고 있는 View객체에 데이터를 연결한다. (=binding)

class MyAdapter(val datas: MutableList<String>) : RecyclerView.Adapter<RecyclerView.ViewHolder>(){
    // ViewHolder, 뷰 객체 초기화
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
        return MyViewHolder(ItemMainBinding.inflate(LayoutInflater.from(parent.context), parent, false))
    }

    // ViewHolder의 뷰 객체에 데이터 바인딩
    override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
        Log.d("kkang", "onBindViewHolder")
        val binding = (holder as MyViewHolder).binding

        // 뷰에 데이터 출력
        binding.itemData.text = datas[position]

        // 뷰에 이벤트 등록
        binding.itemRoot.setOnClickListener {
            Log.d("kkang", "item root click : $position")
        }
    }

    // 총 데이터 개수 반환
    override fun getItemCount(): Int {
        return datas.size
    }
}

 

Adapter 메서드

  • onCreateViewHolder() : RecyclerView가 ViewHolder를 새로 생성할 때 호출하는 메서드. ViewHolder와 그에 연결된 View를 생성하고 초기화한다.
  • onBindViewHolder() : RecyclerView가 ViewHolder에 데이터를 연결할 때 호출하는 메서드. 데이터를 가져와서 그 데이터로 ViewHolder의 레이아웃을 채운다.
    (ex: 이름 목록 데이터를 가져와서 ViewHolder의 TextView 객체를 채움)
  • getItemCount() : 총 데이터 개수 반환, 여기서 반환된 숫자만큼 onBindViewHolder() 함수가 호출되고, 항목을 추가로 표시할 수 없는 상황이면 0을 반환한다.

 

7. RecyclerView 화면 출력

기존에 있던 화면 출력 담당 onCreate() 메서드 내 코드를 수정한다.

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        // RecyclerView 선언된 XML 파일을 메인화면으로 출력
        val binding = ActivityRecyclerviewBinding.inflate(layoutInflater)
        setContentView(binding.root)

        // View 객체에 연결할 데이터 세트
        val datas = mutableListOf<String>()
        for(i in 1..10){
            datas.add("Item $i")
        }

        // RecyclerView 설정
        binding.recyclerView.layoutManager = LinearLayoutManager(this)
        binding.recyclerView.adapter = MyAdapter(datas)
        binding.recyclerView.addItemDecoration(DividerItemDecoration(this, LinearLayoutManager.VERTICAL))
   }
}

 

8. LayoutManager를 사용해 RecyclerView의 항목을 정렬할 수 있다. (=RecyclerView.layoutManager 속성 설정)

binding.recyclerView.layoutManager = LinearLayoutManager(this)
  • LinearLayoutManager : 항목을 1차원 목록으로 정렬
  • GridLayoutManager : 항목을 2차원 그리드로 정렬, 각 행/열의 너비/높이를 자동맞춤
  • StaggeredGridLayoutManager : GridLayoutManger와 유사하나 각 행/열의 너비/높이 자유

 

(좌) LinearLayoutManger 적용 / (우) GridLayoutManager 적용

 

 

9. ItemDecoration으로 RecyclerView에 디자인을 추가할 수 있다. (=RecyclerView.addItemDecoration() 메서드 호출)

binding.recyclerView.addItemDecoration(DividerItemDecoration(this, LinearLayoutManager.VERTICAL))

 

(좌) ItemDecoratoin 적용 안함 / (우) ItemDecoration 적용함

 

메인 소스코드

[MainActivity.kt]

package com.example.androidlab

import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.appcompat.app.AppCompatActivity
import androidx.recyclerview.widget.DividerItemDecoration
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.example.androidlab.databinding.ActivityRecyclerviewBinding
import com.example.androidlab.databinding.ItemMainBinding

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        // RecyclerView 선언된 XML 파일을 메인화면으로 출력
        val binding = ActivityRecyclerviewBinding.inflate(layoutInflater)
        setContentView(binding.root)

        // View 객체에 연결할 데이터 세트
        val datas = mutableListOf<String>()
        for(i in 1..10){
            datas.add("Item $i")
        }

        // RecyclerView 설정
        binding.recyclerView.layoutManager = LinearLayoutManager(this)
        binding.recyclerView.adapter = MyAdapter(datas)
        binding.recyclerView.addItemDecoration(DividerItemDecoration(this, LinearLayoutManager.VERTICAL))
   }
}

// ViewHolder
class MyViewHolder(val binding: ItemMainBinding) : RecyclerView.ViewHolder(binding.root)

// Adapter
class MyAdapter(val datas: MutableList<String>) : RecyclerView.Adapter<RecyclerView.ViewHolder>(){
    // ViewHolder, 뷰 객체 초기화
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
        return MyViewHolder(ItemMainBinding.inflate(LayoutInflater.from(parent.context), parent, false))
    }

    // ViewHolder의 뷰 객체에 데이터 바인딩
    override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
        Log.d("kkang", "onBindViewHolder")
        val binding = (holder as MyViewHolder).binding

        // 뷰에 데이터 출력
        binding.itemData.text = datas[position]

        // 뷰에 이벤트 등록
        binding.itemRoot.setOnClickListener {
            Log.d("kkang", "item root click : $position")
        }
    }

    // 총 데이터 개수 반환
    override fun getItemCount(): Int {
        return datas.size
    }
}

 


참고자료

  • Android Developer - RecyclerView로 동적 목록 만들기, https://developer.android.com/guide/topics/ui/layout/recyclerview?hl=ko
  • Android Developer - GridLayoutManager, https://developer.android.com/reference/androidx/recyclerview/widget/GridLayoutManager?hl=ko 
  • Do it! 깡쌤의 안드로이드 프로그래밍 with Kotlin
728x90