๊นจ์•Œ ๊ฐœ๋… ๐Ÿ“‘/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)๋ฅผ ๋ฐ›๊ฒŒ ๋งŒ๋“ค์–ด๋ณธ๋‹ค.