깨알 개념/Android

[Android] RecyclerView - 기초

interfacer_han 2024. 2. 2. 18:55

#1 ListView vs RecyclerView

화면에 요소(Item)을 100개 표현한다고 해보자. ListView나 GridView 등의 전통적인 Container Widget들은 이 100개의 아이템을 모두 불러온(load)다. 그리고 화면을 스크롤하면 미리 Load되어있는 아이템들이 보이는 방식이다. 메모리 효율이 낮다. 반면, RecyclerView는 아이템들을 미리 Load해두지 않는다. 아이템이 화면이 보이기 직전에, 이전 아이템의 껍데기를 재활용(Recycle)해서 그때 그때 Load한다.
 

#2 Adapter

RecyclerView.Adapter  |  Android Developers

androidx.appsearch.builtintypes.properties

developer.android.com

리사이클러뷰의 아이템 하나하나를 ViewHolder라고 한다. 그리고 RecyclerView의 아이템이 화면에 보이기 직전에 Load되는 것을 총괄하는 주체를 Adapter라고 한다. 이 Adapter는 onCreateViewHolder, onBindViewHolder, getItemCount()의 3가지 핵심 메소드를 가지고 있다. 프로그래머는 이를 오버라이드하여 구현해두어야 한다. 각 메소드의 역할은 다음과 같다.
 

#2-1 onCreateViewHolder(): ViewHolder

MainActivity에서의 onCreate()와 비슷하다. RecyclerView에서 보일 Item인 ViewHolder를 생성한다. 보통 별도의 xml 파일, 예를 들어 list_item.xml을 LayoutInflater 클래스를 이용해 View로 만든다.
 

#2-2 onBindViewHolder()

MainActivity에서의 onStart()와 비슷하다. 이 메소드에서 ViewHolder의 UI를 완성한다. onBindViewHolder()에는 position이라는 이름의 매개변수가 있다. 이 변수는 #1의 화면 속 Item 옆에 써진 숫자를 의미한다. position은 0부터 시작한다. 따라서 #1엔 "Item 0"부터 "Item 99"까지가 존재한다.
 

#2-3 getItemCount(): Int

Adapter가 관리해야하는 ViewHolder의 갯수를 반환한다.
 

#3 RecyclerView 사용하기

#3-1 activity_main.xml에서 RecyclerView 위젯 추가

<?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">

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/recyclerView"
        android:layout_width="0dp"
        android:layout_height="0dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

 

#3-2 list_item.xml 만들기

<?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:layout_width="match_parent"
    android:layout_height="wrap_content">

    <TextView
        android:id="@+id/textItem"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_margin="16sp"
        android:background="#BD5151"
        android:padding="16sp"
        android:text="TextView"
        android:textColor="#FFFFFF"
        android:textSize="26sp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

리사이클러뷰에 담길 아이템의 레이아웃 파일이다.
 

#3-3 MyRecyclerViewAdapter.kt 만들기 - 1

// package com.example.recyclerviewbasics

import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView

class MyRecyclerViewAdapter : RecyclerView.Adapter<MyViewHolder>() {
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
        TODO("Not yet implemented")
    }

    override fun getItemCount(): Int {
        TODO("Not yet implemented")
    }

    override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
        TODO("Not yet implemented")
    }
}

class MyViewHolder(view: View) : RecyclerView.ViewHolder(view) {

}

RecyclerView.Adapter를 상속받아 만든다. 그리고, 리사이클러뷰 속의 아이템인 ViewHolder 클래스도 RecyclerView.ViewHolder를 상속받아 만든다. ViewHolder 클래스를 MyRecyclerViewAdapter.kt 내에 정의하는 게 아니라 MyViewHolder.kt와 같이 별도의 파일을 만들어 그 속에 만들어도 된다. 하지만, 본 게시글에서 사용할 ViewHolder는 ViewHolder가 쓰이는 RecyclerView와 강하게 결합되어 있기 때문에 이렇게 Adapter의 클래스 내에 정의했다. 
 

#3-4 MyRecyclerViewAdapter.kt 만들기 - 2

// package com.example.recyclerviewbasics

import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView

class MyRecyclerViewAdapter : RecyclerView.Adapter<MyViewHolder>() {
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
        val layoutInflater = LayoutInflater.from(parent.context)
        val listItem = layoutInflater.inflate(R.layout.list_item, parent, false)
        return MyViewHolder(listItem)
    }

    override fun getItemCount(): Int {
        return 100
    }

    override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
        holder.textItem.text = "Hello\nitem $position"
    }
}

class MyViewHolder(view: View) : RecyclerView.ViewHolder(view) {
    val textItem: TextView = view.findViewById(R.id.textItem)
}

XML 레이아웃 파일을 실제 뷰 객체로 인스턴스화하는 데 사용하는 클래스인 LayoutInflater를 이용해 아까 만들어둔 list_item.xml을 인스턴스화한다. 여기서 return된 인스턴스는 사용자에게 보이기 직전에 onBindViewHolder에 의해 내부 데이터(여기서는 TextView.text)가 설정된다.
 

#3-5 MainActivity.kt에서 RecyclerView 객체 초기화

// package com.example.recyclerviewbasics

import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        val recyclerView = findViewById<RecyclerView>(R.id.recyclerView)
        recyclerView.layoutManager = LinearLayoutManager(this)
        recyclerView.adapter = MyRecyclerViewAdapter()
    }
}

먼저 activity_main.xml에 있는 리사이클러뷰 위젯을 findViewById로 참조한다. 그리고 해당 위젯의 layoutManager 속성을 LinearLayoutManager로 설정한다. 레이아웃 매니저는 RecyclerView가 어떤 방식으로 보일 지를 정의하는 객체다. 레이아웃 매니저는 기본적으로 LinearLayoutManager, GridLayoutManager, StaggeredGridLayoutManager가 있다.
 
LinearLayoutManager는 아이템들을 수직(세로 스크롤) 또는 수평(가로 스크롤)으로 나란히 배치할 때, GridLayoutManager는 아이템들을 GridView처럼 그리드에 배치할 때, StaggeredGridLayoutManager는 GridLayoutManager와 비슷하지만, 그리드에 들어가는 각 아이템의 높이를 다르게 설정할 때 사용하는 레이아웃 매니저다. 이 3개의 기본 매니저에 더해 구현이 조금 복잡하지만, 사용자 정의(Custom) 레이아웃 매니저를 만들어 layoutManager 속성에 할당해줄 수도 있다. 이러한 레이아웃적 유연성은 메모리 성능 향상 외 리사이클러뷰의 장점이다. 
 
마지막으로, 리사이클러뷰가 어떻게 동작할 지를 관리하는 객체인 Adapter를 할당한다.
 

#4 작동 확인

#4-1 LinearLayoutManager

 

#4-2 GridLayoutManager

...

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        ...
        recyclerView.layoutManager = GridLayoutManager(this, 3)
        ...
    }
}

GridLayoutManager를 사용한 경우의 동작이다. 3은 내가 임의로 정한 열의 갯수다. 따라서, 3열로 출력될 것이다.
 

 

#5 요약

리사이클러뷰는 메모리 효율이 높다.
 

#6 완성된 앱

https://github.com/Kanmanemone/android-practice/tree/master/recycler-view/RecyclerViewBasics
 

#7 이어지는 글

[Android] RecyclerView - Adapter에 인자(Argument) 전달

#1 이전 글 [Android] RecyclerView - 기초 #1 ListView vs RecyclerView 화면에 요소(Item)을 100개 표현한다고 해보자. ListView나 GridView 등의 전통적인 Container Widget들은 이 100개의 아이템을 모두 불러온(load)다. 그

kenel.tistory.com

RecyclerView의 Adapter가 인자(Argument)를 받게 만들어본다.