Study Record

[안드로이드] RecyclerView 살펴보기 본문

안드로이드

[안드로이드] RecyclerView 살펴보기

초코초코초코 2023. 7. 18. 18:12
728x90

😶 RecyclerView 개요

스마트폰에서 자주 사용하는 앱은 거의 목록이 하나 이상 있다. 목록은 단순한 단어나 구문 목록부터 텍스트와 이미지가 포함된 카드와 같은 더 복잡한 항목을 포함한다. 이렇게 어떤 콘텐츠든 상관없이 데이터 목록을 표시하는 것이 Android 에서 가장 일반적인 UI 작업이다.

 

Android 는 목록이 있는 앱을 빌드할 수 있도록 RecyclerView 를 제공한다. RecyclerView 는 화면에서 목록 항목이 스크롤되면 다음 표시할 목록 항목에 이전 뷰를 재사용하기 때문에 처리 시간을 단축하고 목록이 더 원활하게 스크롤되도록 도와준다. 

 

 

목록 예시)

 

 

 

 

😶 RecyclerView 동작 원리

 

 

RecyclerView 에서는 공통된 유형의 데이터 항목을 목록으로 표현한다.

item 은 표시할 목록의 단일 데이터 항목을 나타내니다. 예를 들어, 책의 이름, 책의 이미지, 책 소개를 보여주는 목록이라면 한 목록 당 책의 이름, 이미지, 소개 등의 정보를 가지고 있는 데이터 하나를 item 이라고 할 수 있다.

 

ViewHolder 는 데이터의 리스트인 item 을 보여줄 View 혹은 재사용할 View 들의 pool 이다. RecyclerView 는 항목이 스크롤되면 다음 표시할 항목에 이전 뷰를 재사용한다고 개요에서 설명한 바가 있다. 여기서 View 는 item 정보를 가져와 각 항목을 어떻게 보여줄 것인지에 대한 디자인이 담겨있다.

 

Adapter 는 데이터를 가져와 RecyclerView 에서 보여줄 준비를 한다.

 

RecyclerView 는 화면에 항목(View) 를 보여준다.

 

 

 

 

😶 RecyclerView 사용하기

 

1. 데이터 정의(item 정의)

공통된 유형의 데이터를 항목으로 가지고 있는 목록을 표시하는 RecyclerView 를 사용할 때는 공통된 유형의 데이터 타입을 따로 클래스를 만드는 것이 좋다. 개발자는 데이터를 모델링하거나 표현하는 클래스의 패키지 이름으로 "model" 을 사용하는 경우가 많다.

package com.example.affirmations.model

data class Snack(val name: String)

 

 

2. RecyclerView (xml 파일에 배치)

xml 파일에 RecyclerView 를 배치한다.

<androidx.recyclerview.widget.RecyclerView
    android:id="@+id/recycler_view"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:scrollbars="vertical"
    app:layoutManager="LinearLayoutManager" />

android:scrollbars 속성으로 가로 스크롤(horizontal), 세로 스크롤(vertical) 로 나뉠 수 있다.

android:layoutManager 속성으로 목록의 모양을 정의할 수 있는데 LinearLayout 은 선형 모양이다.

 

 

3. View 만들기

item 데이터을 항목 디자인에 적용할 View 를 만들어야 한다. 원하는 이름으로 res/layout 디렉터리의 xml 파일을 만든다. 이 View 는 RecyclerView 에서 직접 관리하지 않고 ViewHolder 에서 관리한다.

> res/layout/list_item.xml

<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/item_title"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content" />

 

 

4. Adapter 와 ViewHolder 만들기

전체적인 코드는 다음과 같다. Adapter 클래스 안에 ViewHolder 를 중첩하여 정의하는 것은 개발자가 코드를 보기 쉽게 하기 위해서이다.

class SnackAdapter(private val data: List<Snack>) :
    RecyclerView.Adapter<SnackAdapter.SnackViewHolder>() {

    class SnackViewHolder(val view: View) : RecyclerView.ViewHolder(view) {
        val textView = view.findViewById<TextView>(R.id.list_item_title)

        fun bind(snack: Snack) {
            textView.text = snack.name
        }
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SnackViewHolder {
        // create a new view
        val adapterLayout = LayoutInflater.from(parent.context)
            .inflate(R.layout.item_snack, parent, false)

        return SnackViewHolder(adapterLayout)
    }

    override fun getItemCount(): Int = data.size

    override fun onBindViewHolder(holder: SnackViewHolder, position: Int) {
        holder.bind(data[position])
    }

}

 

먼저, Adapter 는 ViewHodler 와 item 을 참고하여 RecyclerView 에 항목을 표시할 준비를 하고 ViewHolder 는 사용/재사용할 View 를 관리하는 역할을 한다.

 

따라서, Adapter 의 정의 부분을 보면 각 item 의 정보를 담고 있는 변수를 data 로 받고 RecyclerView.Adapter 를 상속받을 때 사용할 ViewHolder 를 표시해 준다.

class SnackAdapter(private val data: List<Snack>) :
    RecyclerView.Adapter<SnackAdapter.SnackViewHolder>() {

    class SnackViewHolder(val view: View) : RecyclerView.ViewHolder(view) {
        val textView = view.findViewById<TextView>(R.id.list_item_title)

        fun bind(snack: Snack) {
            textView.text = snack.name
        }
    }

}

 

RecyclerView.Adapter 를 상속받은 클래스는 반드시 재정의해야 하는 함수 getItemCount() 와 onCreateViewHolder(), onBindViewHolder() 가 있다.

class SnackAdapter(private val data: List<Snack>) :
    RecyclerView.Adapter<SnackAdapter.SnackViewHolder>() {

    class SnackViewHolder(val view: View) : RecyclerView.ViewHolder(view) {
        val textView = view.findViewById<TextView>(R.id.list_item_title)

        fun bind(snack: Snack) {
            textView.text = snack.name
        }
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SnackViewHolder {
        // create a new view
        val adapterLayout = LayoutInflater.from(parent.context)
            .inflate(R.layout.item_snack, parent, false)

        return SnackViewHolder(adapterLayout)
    }

    override fun getItemCount(): Int = data.size

    override fun onBindViewHolder(holder: SnackViewHolder, position: Int) {
        holder.bind(data[position])
    }

}

 

 

하나씩 함수의 역할과 의미를 알아보면,

 

😶 getItemCount()

getItemCount() 는 목록에 포함된 항목의 개수를 리턴 받는다. 따라서, item 의 개수인 data.size 를 리턴하고 있다.

class SnackAdapter(private val data: List<Snack>) :
    RecyclerView.Adapter<SnackAdapter.SnackViewHolder>() {

    override fun getItemCount(): Int = data.size

}

 

😶 onCreateViewHolder()

onCreateViewHolder() 는 RecyclerView 의 새 ViewHolder 를 만들기 위해 레이아웃 관리자가 호출한다. 재사용할 수 있는 기존 ViewHolder 가 있는 경우 호출되지 않는다. 여기서 parent 는 새 목록 항목 보기가 자식으로 연결된 보기 그룹으로 부모는 RecyclerView 이다. ViewHolder 를 리턴 받아야 하므로 미리 만들어둔 SnackViewHolder 를 리턴한다.

class SnackAdapter(private val data: List<Snack>) :
    RecyclerView.Adapter<SnackAdapter.SnackViewHolder>() {
    ...
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SnackViewHolder {
        // create a new view
        val adapterLayout = LayoutInflater.from(parent.context)
            .inflate(R.layout.item_snack, parent, false)

        return SnackViewHolder(adapterLayout)
    }
    ...
}

 

ViewHolder 는 item 데이터를 가지고 화면에 꾸밀 View 를 관리하기 때문에 RecyclerView.ViewHolder 는 화면에 꾸밀 View 를 인자로 받는다. 이 View 를 만들 때 LayoutInflater.from(context).inflate() 함수를 이용한다.

class SnackAdapter(private val data: List<Snack>) :
    RecyclerView.Adapter<SnackAdapter.SnackViewHolder>() {

    class SnackViewHolder(val view: View) : RecyclerView.ViewHolder(view) {
        val textView = view.findViewById<TextView>(R.id.list_item_title)

        fun bind(snack: Snack) {
            textView.text = snack.name
        }
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SnackViewHolder {
        // create a new view
        val adapterLayout = LayoutInflater.from(parent.context)
            .inflate(R.layout.item_snack, parent, false)

        return SnackViewHolder(adapterLayout)
    }

}

inflate() 함수의 첫 번째 매개변수(resource)는 로드할 XML 레이아웃 리소스 ID 를 의미하고,

두 번째 매개변수(root)는 생성된 계층의 부모(attachToRoot = true) or 반환된 계층 루트에 대한 LayuotParams 값 집합을 제공하는 개체(attachToRoot = false)를 의미한다.

세 번째 매개변수(attachToRoot)가 true 일 경우, 계층 구조를 root(두 번째 매개변수)에 연결하고 false 인 경우 root(두 번째 인자)에 대해 올바른 하위 클래스를 만드는 데에만 사용한다.

 

세 번째 매개변수(attachToRoot)가 false 이므로, 두 번째 매개변수(root) 는 LayoutParams 값 집합만을 제공한다.

 

 

😶 onBindViewHodler()

onBindViewHodler() 는 목록에서 항목들을 보여줄 때 보여줄 항목의 내용을 바꿀 때(ex. 스크롤하여 보여줘야 할 항목을 변경해야할 경우 레이아웃 관리자에 의해 호출된다. onBindViewHolder() 는 두 개의 파라미터를 갖는다. 첫 번째는 onCreateViewHolder() 에서 생성한 ViewHodler 이고 두 번째 파라미터는 현재 보이는 목록의 position 이다. 

class SnackAdapter(private val data: List<Snack>) :
    RecyclerView.Adapter<SnackAdapter.SnackViewHolder>() {

    class SnackViewHolder(val view: View) : RecyclerView.ViewHolder(view) {
        val textView = view.findViewById<TextView>(R.id.list_item_title)

        fun bind(snack: Snack) {
            textView.text = snack.name
        }
    }

    override fun onBindViewHolder(holder: SnackViewHolder, position: Int) {
        holder.bind(data[position])
    }

}

 

 

5. Activity 에서 Adapter 정의하기

RecyclerView 에 adapter 를 정의한다.

class MainActivity : AppCompatActivity() {
    private lateinit var binding: ActivityMainBinding
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = DataBindingUtil.setContentView<ActivityMainBinding>(this, R.layout.activity_main)

        binding.recyclerView.adapter = SnackAdapter(listOf(Snack("happy"), Snack("hop"), Snack("dfsfdf")))
        binding.recyclerView.setHasFixedSize(true)
    }

}

setHasFixedSize 속성은 RecyclerView 의 레이아웃 크기가 고정되어 있을 경우 성능을 개선하기 위해 true 로 설정할 수 있다. RecyclerView 의 크기가 변경되지 않는다는 것을 아는 경우 true 로 설정해야 한다.

 

 

 

😶 RecyclerView 동작 보충 설명

 

RecyclerView 는 화면에서 목록 항목이 스크롤되면 다음 표시할 목록 항목에 이전 뷰를 재사용하기 때문에 처리 시간을 단축하고 목록이 더 원활하게 스크롤되도록 도와준다.

 

이것이 가능한 이유는 항목(View)을 관리하는 ViewHolder 가 item(항목에 대한 데이터)의 개수보다 적게 생성(onCreateVieWHodler()) 되고 해당 item 에 따라 View 를 표시해야 할 때(onBindViewHolder()) 이전 ViewHolder 를 재사용하기 때문이다.

 

 

 

😶 RecyclerView 여러가지 모양

RecyclerView 의 app:layoutManager 속성을 이용하면 여러가지 목록 모양을 배치할 수 있다. LinearLayoutManager , GridLayoutManager , StaggerdGridLayoutManager 가 있다.

 

 

 

 

LinearLayoutManager

<androidx.recyclerview.widget.RecyclerView
    android:id="@+id/horizontal_recycler_view"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical"
    android:scrollbars="vertical"
    app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
    android:layout_gravity="center"/>

선형으로 목록을 배치한다.

 

 

GridLayoutManager

<androidx.recyclerview.widget.RecyclerView
    android:id="@+id/grid_recycler_view"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    app:layoutManager="androidx.recyclerview.widget.GridLayoutManager"
    app:spanCount="2"/>

app:spanCount 속성으로 한 줄이아닌 여러줄로 항목을 배치할 수 있다.

 

 

 

StaggerdGridLayoutManager

<androidx.recyclerview.widget.RecyclerView
    android:id="@+id/vertical_recycler_view"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:scrollbars="vertical"
    app:layoutManager="androidx.recyclerview.widget.StaggeredGridLayoutManager"
    app:spanCount="2"/>

GridLayoutManager 와 비슷한데 배칠될 항목의 크기가 제각각일 경우 지그재그 형식으로 보여진다.

 

 

 

출처

 

RecyclerView를 사용하여 스크롤 가능한 목록 표시하기  |  Android 개발자  |  Android Developers

이 Codelab에서는 RecyclerView에 텍스트 목록을 표시하는 앱을 만듭니다.

developer.android.com

 

 

728x90