RecyclerView를 사용하여 스크롤 가능한 목록 표시하기

스마트폰에서 자주 사용하는 앱을 생각해보면 거의 모든 앱마다 목록이 하나 이상 있습니다. 통화 기록 화면, 연락처 앱, 즐겨 사용하는 소셜 미디어 앱은 모두 데이터 목록을 표시합니다. 아래 스크린샷처럼, 일부 앱은 단순한 단어나 구문 목록을 표시하고 다른 일부 앱은 텍스트와 이미지가 포함된 카드와 같은 더 복잡한 항목을 표시합니다. 어떤 콘텐츠이든 상관없이 데이터 목록을 표시하는 것은 Android에서 가장 일반적인 UI 작업 중 하나입니다.

cf10a913f9db0ee4.png

Android는 목록이 있는 앱을 빌드할 수 있도록 RecyclerView를 제공합니다. RecyclerView는 화면에서 스크롤된 뷰를 재사용하거나 재활용하여 목록이 큰 경우에도 매우 효율적으로 작동하도록 설계되었습니다. 화면에서 목록 항목이 스크롤되면 RecyclerView는 표시할 다음 목록 항목에 이 뷰를 재사용합니다. 다시 말해서, 항목이 화면에 스크롤되는 새로운 콘텐츠로 채워집니다. 이 RecyclerView 동작은 처리 시간을 크게 단축하고 목록이 더 원활하게 스크롤되도록 도와줍니다.

한 뷰가 아래의 순서대로 ABC 데이터로 채워진 것을 볼 수 있습니다. 이 뷰가 화면에서 스크롤된 후에는 RecyclerView는 이 뷰를 새 데이터인 XYZ에 재사용합니다.

dcf4599789b9c2a1.png

이 Codelab에서는 Affirmations 앱을 빌드합니다. Affirmations는 스크롤 목록에 긍정적인 확인 10개를 텍스트로 표시하는 간단한 앱입니다. 후속 Codelab에서는 한 걸음 더 나아가 영감을 주는 이미지를 각 확인에 추가하고 앱 UI를 깔끔하게 만듭니다.

기본 요건

  • Android 스튜디오에서 템플릿으로 프로젝트를 만듭니다.
  • 앱에 문자열 리소스를 추가합니다.
  • 레이아웃을 XML로 정의합니다.
  • Kotlin의 클래스 및 상속을 이해합니다(추상 클래스 포함).
  • 기존 클래스에서 상속하고 메서드를 재정의합니다.
  • Android 프레임워크에서 제공하는 클래스는 developer.android.com의 문서를 참고하세요.

학습할 내용

  • RecyclerView를 사용하여 데이터 목록을 표시하는 방법
  • 코드를 패키지로 구성하는 방법
  • RecyclerView와 함께 어댑터를 사용하여 개별 목록 항목의 모양을 맞춤설정하는 방법

빌드할 항목

  • RecyclerView를 사용하여 확인 문자열 목록을 표시하는 앱

요구사항

  • Android 스튜디오 버전 4.1 이상이 설치된 컴퓨터

Empty Activity 프로젝트 만들기

새 프로젝트를 만들기 전에 사용 중인 Android 스튜디오 버전이 4.1 이상인지 확인하세요.

  1. Android 스튜디오에서 Empty Activity 템플릿을 사용하여 새 Kotlin 프로젝트를 시작합니다.
  2. 앱의 Name으로 Affirmations를 입력하고 Package namecom.example.affirmations를 입력한 후 Minimum SDKAPI Level 19를 선택합니다.
  3. Finish를 클릭하여 프로젝트를 만듭니다.

Affirmations 앱을 만드는 다음 단계는 리소스를 추가하는 것입니다. 프로젝트에 다음을 추가합니다.

  • 앱에서 확인으로 표시될 문자열 리소스
  • 앱에 확인 목록을 제공하는 데이터 소스

확인 문자열 추가하기

  1. Project 창에서 app > res > values > strings.xml을 엽니다. 현재 이 파일에는 앱의 이름인 단일 리소스가 있습니다.
  2. strings.xml에서 다음 확인을 개별 문자열 리소스로 추가합니다. 이름을 affirmation1, affirmation2 등으로 지정합니다.

확인 텍스트

I am strong.
I believe in myself.
Each day is a new opportunity to grow and be a better version of myself.
Every challenge in my life is an opportunity to learn from.
I have so much to be grateful for.
Good things are always coming into my life.
New opportunities await me at every turn.
I have the courage to follow my heart.
Things will unfold at precisely the right time.
I will be present in all the moments that this day brings.

완료되면 strings.xml 파일은 다음과 같습니다.

<resources>
    <string name="app_name">Affirmations</string>
    <string name="affirmation1">I am strong.</string>
    <string name="affirmation2">I believe in myself.</string>
    <string name="affirmation3">Each day is a new opportunity to grow and be a better version of myself.</string>
    <string name="affirmation4">Every challenge in my life is an opportunity to learn from.</string>
    <string name="affirmation5">I have so much to be grateful for.</string>
    <string name="affirmation6">Good things are always coming into my life.</string>
    <string name="affirmation7">New opportunities await me at every turn.</string>
    <string name="affirmation8">I have the courage to follow my heart.</string>
    <string name="affirmation9">Things will unfold at precisely the right time.</string>
    <string name="affirmation10">I will be present in all the moments that this day brings.</string>
</resources>

이제 문자열 리소스를 추가했으므로 코드에서 R.string.affirmation1 또는 R.string.affirmation2로 참조할 수 있습니다.

새 패키지 만들기

코드를 논리적으로 구성하면 다른 개발자들도 코드를 이해하고 유지관리하고 확장할 수 있습니다. 서류를 파일과 폴더로 정리하는 것과 동일한 방법으로 코드를 파일 및 패키지로 구성할 수 있습니다.

패키지란?

  1. Android 스튜디오의 Project 창(Android)에서 app > java 아래에 있는 새 프로젝트 파일을 살펴보고 Affirmations 앱을 찾습니다. 아래의 스크린샷과 유사하게 패키지 세 개, 즉 코드용 패키지 한 개(com.example.affirmations)와 테스트 파일용 패키지 두 개(com.example.affirmations (androidTest)com.example.affirmations (test))가 표시됩니다.

809a0d77a0759dc5.png

  1. 패키지 이름은 마침표로 구분된 여러 단어로 구성됩니다.

다음 두 가지 방법으로 패키지를 사용할 수 있습니다.

  • 코드의 여러 부분별로 서로 다른 패키지를 만듭니다. 예를 들어, 개발자는 데이터 작업에 사용하는 클래스와 UI를 서로 다른 패키지로 빌드하는 클래스로 구분하는 경우가 많습니다.
  • 코드에 있는 다른 패키지의 코드를 사용합니다. 다른 패키지의 클래스를 사용하려면 빌드 시스템의 종속 항목에 클래스를 정의해야 합니다. 또한 정규화된 이름(예: android.widget.TextView) 대신 축약 이름(예: TextView)을 사용할 수 있도록 클래스를 코드에 import하는 것도 표준 관행입니다. 예를 들어 클래스에 sqrt(import kotlin.math.sqrt) 및 View(import android.view.View) 같은 import 문을 이미 사용한 경우를 예로 들 수 있습니다.

Affirmations 앱에서 Android 클래스와 Kotlin 클래스를 가져오는 것 외에도 앱을 여러 패키지로 구성합니다. 앱의 클래스가 많지 않더라도 패키지를 사용하여 기능별로 클래스를 그룹화하는 것이 좋습니다.

패키지 이름 지정하기

패키지 이름은 전역적으로 고유하기만 하다면 어떤 이름이든 사용할 수 있습니다. 위치에 관계없이 게시된 다른 패키지의 이름이 같을 수는 없습니다. 패키지가 너무 많아서 임의의 고유한 이름을 고안하기가 어렵기 때문에 프로그래머는 패키지 이름을 더 쉽게 만들고 이해할 수 있도록 이름 지정 규칙을 사용합니다.

  • 패키지 이름은 일반적으로 일반에서 구체적인 순서로 구성되며 이름의 각 부분을 소문자로 표기하고 마침표로 구분합니다. 중요: 마침표는 이름의 일부일 뿐이며 코드 내의 계층 구조를 나타내거나 폴더 구조를 지시하지 않습니다.
  • 인터넷 도메인은 전역적으로 고유하므로, 이름 첫 부분에 개발자의 도메인이나 비즈니스의 도메인을 사용하는 것이 규칙입니다.
  • 패키지 이름을 선택하여 패키지에 포함된 내용 및 패키지 간의 관계를 표시할 수 있습니다.
  • 이런 코드의 예로, com.example 다음에 앱 이름을 사용하는 것이 일반적입니다.

다음은 사전 정의된 패키지 이름의 예와 내용입니다.

  • kotlin.math - 수학 함수 및 상수
  • android.widget - 뷰(예: TextView)

패키지 만들기

  1. Android 스튜디오의 Project 창에서 app > java > com.example.affirmations를 마우스 오른쪽 버튼으로 클릭하고 New > Package를 선택합니다.

39f35a81a574b7ee.png

  1. New Package 팝업에서 제안된 패키지 이름 접두어를 확인할 수 있습니다. 패키지 이름의 첫 부분은 마우스 오른쪽 버튼으로 클릭한 패키지의 이름입니다. 패키지 이름이 패키지의 계층 구조를 만들지는 않지만, 콘텐츠의 관계와 구성을 나타내기 위해 이름의 일부를 재사용할 수 있습니다.
  2. 팝업에서 제안된 패키지 이름 끝에 model을 추가합니다. 개발자는 데이터를 모델링하거나 표현하는 클래스의 패키지 이름으로 model을 사용하는 경우가 많습니다.

3d392f8da53adc6f.png

  1. Enter 키를 누릅니다. 그러면 com.example.affirmations(루트) 패키지 아래에 새 패키지가 생성됩니다. 이 새 패키지에는 앱에서 정의된 모든 데이터 관련 클래스가 포함됩니다.

Affirmation 데이터 클래스 만들기

이 작업에서는 Affirmation.이라는 클래스를 만듭니다. Affirmation의 객체 인스턴스 하나는 확인 하나를 나타내며 확인이 포함된 문자열의 리소스 ID를 포함합니다.

  1. com.example.affirmations.model 패키지를 마우스 오른쪽 버튼으로 클릭하고 New > Kotlin File/Class를 선택합니다.

d9b07905f456ab1.png

  1. 팝업에서 Class를 선택하고 클래스 이름으로 Affirmation을 입력합니다. 그러면 model 패키지에 Affirmation.kt라는 새 파일이 생성됩니다.
  2. 클래스 정의 앞에 data 키워드를 추가하여 Affirmation을 데이터 클래스로 만듭니다. 그러면 오류가 발생합니다. 왜냐하면 데이터 클래스에 속성이 하나 이상 정의되어 있어야 하기 때문입니다.

Affirmation.kt

package com.example.affirmations.model

data class Affirmation {
}

Affirmation 인스턴스를 만들 때 확인 문자열의 리소스 ID를 전달해야 합니다. 리소스 ID는 정수입니다.

  1. val 정수 매개변수 stringResourceIdAffirmation 클래스의 생성자에 추가합니다. 이렇게 하면 오류가 사라집니다.
package com.example.affirmations.model

data class Affirmation(val stringResourceId: Int)

데이터 소스가 되는 클래스 만들기

앱에 표시되는 데이터는 다른 소스에서 가져올 수도 있습니다(예: 앱 프로젝트 내의 소스 또는 데이터 다운로드를 위해 인터넷에 연결해야 하는 외부 소스). 그 결과로, 필요한 형식이 아닌 데이터를 얻을 수도 있습니다. 앱의 나머지 부분은 데이터의 출처나 데이터의 원래 형식을 염려하지 않아야 합니다. 앱의 데이터를 준비하는 별도의 Datasource 클래스에 이러한 데이터 준비를 숨길 수 있으며 숨겨야 합니다.

데이터 준비는 별개의 문제이므로, Datasource 클래스를 별도의 data 패키지에 포함하세요.

  1. Android 스튜디오의 Project 창에서 app > java > com.example.affirmations를 마우스 오른쪽 버튼으로 클릭하고 New > Package를 선택합니다.
  2. 패키지 이름의 마지막 부분으로 data를 입력합니다.
  3. data 패키지를 마우스 오른쪽 버튼으로 클릭하고 new Kotlin File/Class를 선택합니다.
  4. 클래스 이름으로 Datasource를 입력합니다.
  5. Datasource 클래스 내부에 loadAffirmations()라는 함수를 만듭니다.

loadAffirmations() 함수는 Affirmations의 목록을 반환해야 합니다. 목록을 만들고 이 목록에 각 리소스 문자열의 Affirmation 인스턴스를 입력하면 됩니다.

  1. List<Affirmation>loadAffirmations() 메서드의 반환 유형으로 선언합니다.
  2. loadAffirmations() 본문에 return 문을 추가합니다.
  3. return 키워드 다음에 listOf<>()를 호출하여 List를 만듭니다.
  4. 꺾쇠 괄호 <> 안에 목록 항목 유형을 Affirmation으로 지정합니다. 필요한 경우 com.example.affirmations.model.Affirmation을 가져옵니다.
  5. 괄호 안에 Affirmation을 만들고 아래와 같이 리소스 ID로 R.string.affirmation1을 전달합니다.
Affirmation(R.string.affirmation1)
  1. 나머지 Affirmation 객체를 쉼표로 구분하여 모든 확인 목록에 추가합니다. 완성된 코드는 다음과 같습니다.

Datasource.kt

package com.example.affirmations.data

import com.example.affirmations.R
import com.example.affirmations.model.Affirmation

class Datasource {

    fun loadAffirmations(): List<Affirmation> {
        return listOf<Affirmation>(
            Affirmation(R.string.affirmation1),
            Affirmation(R.string.affirmation2),
            Affirmation(R.string.affirmation3),
            Affirmation(R.string.affirmation4),
            Affirmation(R.string.affirmation5),
            Affirmation(R.string.affirmation6),
            Affirmation(R.string.affirmation7),
            Affirmation(R.string.affirmation8),
            Affirmation(R.string.affirmation9),
            Affirmation(R.string.affirmation10)
        )
    }
}

(선택사항) TextView에 확인 목록의 크기 표시하기

확인 목록을 만들 수 있는지 확인하려면 loadAffirmations()를 호출하고 반환된 확인 목록의 크기를 Empty Activity 앱 템플릿과 함께 제공되는 TextView에 표시할 수 있습니다.

  1. layouts/activity_main.xml에서 템플릿과 함께 제공된 TextViewtextviewid를 제공합니다.
  2. MainActivity에서 기존 코드 뒤에 있는 onCreate() 메서드에 textview 참조를 가져옵니다.
val textView: TextView = findViewById(R.id.textview)
  1. 그런 다음 확인 목록의 크기를 생성하여 표시하는 코드를 추가합니다. Datasource를 만들고, loadAffirmations()를 호출하고, 반환된 목록의 크기를 가져와서 문자열로 변환하고 textViewtext로 할당합니다.
textView.text = Datasource().loadAffirmations().size.toString()
  1. 앱을 실행합니다. 화면이 아래와 같습니다.

b4005973d4a0efc8.png

  1. 방금 MainActivity에 추가한 코드를 삭제합니다.

이 작업에서는 RecyclerView 목록을 설정하여 Affirmations 목록을 표시합니다.

RecyclerView를 만들고 사용하는 데는 많은 부분이 관련됩니다. 이러한 부분을 분업이라고 생각할 수 있습니다. 개요를 보여주는 아래 다이어그램에서 구현하는 각 부분에 관해 자세히 알아볼 수 있습니다.

  • 항목 - 표시할 목록의 단일 데이터 항목입니다. 앱의 Affirmation 객체 하나를 나타냅니다.
  • 어댑터 - RecyclerView에서 표시할 수 있도록 데이터를 가져와 준비합니다.
  • ViewHolder - 확인을 표시하기 위해 사용하거나 재사용할 RecyclerView용 뷰의 풀입니다.
  • RecyclerView - 화면에 표시되는 뷰입니다.

4e9c18b463f00bf7.png

레이아웃에 RecyclerView 추가하기

Affirmations 앱은 MainActivity라는 단일 활동으로 구성되며, 앱의 레이아웃 파일의 이름은 activity_main.xml입니다. 먼저 MainActivity의 레이아웃에 RecyclerView를 추가해야 합니다.

  1. activity_main.xml을 엽니다(app > res > layout > activity_main.xml).
  2. 아직 사용하지 않은 경우 Split 뷰로 전환합니다.

7e7c3e8429267dac.png

  1. TextView를 삭제합니다.

현재 레이아웃은 ConstraintLayout을 사용합니다. 한 레이아웃에 하위 뷰 여러 개를 배치하려면 ConstraintLayout이 가장 적합하고 유연합니다. 레이아웃에 단일 하위 뷰 RecyclerView만 있으므로, 단일 하위 뷰를 보유하는 데 사용해야 하는 더 간단한 ViewGroupFrameLayout으로 전환할 수 있습니다.

9e6f235be5fa31a8.png

  1. XML에서 ConstraintLayoutFrameLayout으로 바꿉니다. 완성된 레이아웃은 아래와 같습니다.

activity_main.xml

<?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=".MainActivity">
</FrameLayout>
  1. Design 뷰로 전환합니다.
  2. Palette에서 Containers를 선택하고 RecyclerView를 찾습니다.
  3. RecyclerView를 레이아웃으로 드래그합니다.
  4. Add Project Dependency 팝업이 표시되면 OK를 클릭합니다. (팝업이 표시되지 않는 경우 별도의 조치가 필요하지 않습니다.)
  5. Android 스튜디오가 완료되고 RecyclerView가 레이아웃에 표시될 때까지 기다립니다.
  6. RecyclerView가 전체 화면을 채울 수 있도록 필요한 경우 RecyclerViewlayout_width 속성과 layout_height 속성을 match_parent로 변경합니다.
  7. RecyclerView의 리소스 ID를 recycler_view로 설정합니다.

RecyclerView는 선형 목록이나 그리드와 같은 다양한 방식으로 항목을 표시하도록 지원합니다. 항목 정렬은 LayoutManager에서 처리합니다. Android 프레임워크에서는 기본 항목 레이아웃을 위한 레이아웃 관리자가 제공됩니다. Affirmations 앱은 항목을 세로 목록으로 표시하므로 LinearLayoutManager를 사용할 수 있습니다.

  1. 다시 Code 뷰로 전환합니다. XML 코드의 RecyclerView 요소 내부에 다음과 같이 LinearLayoutManagerRecyclerView의 레이아웃 관리자 속성으로 추가합니다.
app:layoutManager="LinearLayoutManager"

화면보다 긴 항목의 세로 목록을 스크롤할 수 있으려면 세로 스크롤바를 추가해야 합니다.

  1. RecyclerView 내부에 vertical로 설정된 android:scrollbars 속성을 추가합니다.
android:scrollbars="vertical"

최종 XML 레이아웃은 다음과 같습니다.

activity_main.xml

<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=".MainActivity">

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

</FrameLayout>
  1. 앱을 실행합니다.

프로젝트가 문제없이 컴파일되고 실행되어야 합니다. 하지만 코드의 중요한 부분이 누락되었기 때문에 앱에 흰색 배경만 표시됩니다. 이제 데이터의 소스와 RecyclerView가 레이아웃에 추가되었지만, RecyclerView에는 Affirmation 객체를 표시하는 방법에 관한 정보가 없습니다.

RecyclerView용 어댑터 구현하기

앱에는 Datasource에서 데이터를 가져와 각 AffirmationRecyclerView에 항목으로 표시할 수 있도록 형식을 지정하는 방법이 필요합니다.

어댑터는 데이터를 RecyclerView에서 사용할 수 있는 형식으로 조정하는 설계 패턴입니다. 이 경우에는 RecyclerView.에 표시할 수 있도록 loadAffirmations()에서 반환된 목록에서 Affirmation 인스턴스를 가져와 목록 항목 뷰로 전환하는 어댑터가 필요합니다.

앱을 실행하면 RecyclerView가 어댑터를 사용하여 화면에 데이터를 표시하는 방법을 파악합니다. RecyclerView는 목록의 첫 번째 데이터 항목을 위한 새 목록 항목 뷰를 만들도록 어댑터에 요청합니다. 뷰가 생성된 후에는 항목을 그리기 위한 데이터를 제공하도록 어댑터에 요청합니다. 이 프로세스는 RecyclerView에 화면을 채울 뷰가 더 이상 필요하지 않을 때까지 반복됩니다. 한 번에 목록 항목 뷰 세 개만 화면에 들어가는 경우 RecyclerView는 전체 목록 항목 뷰 10개가 아닌 3개만 준비하도록 어댑터에 요청합니다.

이 단계에서는 RecyclerView에 표시될 수 있도록 Affirmation 객체 인스턴스를 조정하는 어댑터를 빌드합니다.

어댑터 만들기

어댑터에는 여러 부분이 있으며, 지금까지 이 과정에서 완료한 것보다 더 복잡한 대량의 코드를 작성하겠습니다. 처음에 세부정보를 모두 이해하지 못해도 괜찮습니다. RecyclerView로 이 앱 전체를 완료하면 모든 부분이 어떻게 유기적으로 작동하는지 더 잘 이해할 수 있습니다. 향후 RecyclerView로 만드는 앱의 기반으로 이 코드를 다시 사용할 수도 있습니다.

항목의 레이아웃 만들기

RecyclerView의 각 항목에는 고유한 레이아웃이 있으며, 이 레이아웃은 별도의 레이아웃 파일에서 정의합니다. 문자열만 표시하므로 항목 레이아웃에 TextView를 사용할 수 있습니다.

  1. res > layout에서 list_item.xml이라는 새로운 빈 File을 만듭니다.
  2. Code 뷰에서 list_item.xml을 엽니다.
  3. id item_title을 사용하여 TextView를 추가합니다.
  4. 아래 코드와 같이 layout_widthlayout_heightwrap_content를 추가합니다.

나중에 이 목록 항목 레이아웃이 확장되어 상위 RecyclerView에 하위 요소로 추가되므로 레이아웃 주위에 ViewGroup이 필요하지 않습니다.

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

또는 File > New > Layout Resource File을 사용하고 File namelist_item.xml로, TextViewRoot element로 지정했을 수도 있습니다. 그러면 생성된 코드를 위 코드와 일치하도록 업데이트합니다.

dbb34ca516c804c6.png

ItemAdapter 클래스 만들기

  1. Android 스튜디오의 Project 창에서 app > java > com.example.affirmations를 마우스 오른쪽 버튼으로 클릭하고 New > Package를 선택합니다.
  2. 패키지 이름의 마지막 부분으로 adapter를 입력합니다.
  3. adapter 패키지를 마우스 오른쪽 버튼으로 클릭하고 New > Kotlin File/Class를 선택합니다.
  4. 클래스 이름으로 ItemAdapter를 입력하면 완료되고 ItemAdapter.kt 파일이 열립니다.

확인 목록을 어댑터에 전달할 수 있도록 ItemAdapter의 생성자에 매개변수를 추가해야 합니다.

  1. ItemAdapter 생성자에 dataset라는 val(List<Affirmation> 유형)인 매개변수를 추가합니다. 필요한 경우 Affirmation을 가져옵니다.
  2. dataset는 이 클래스 내에서만 사용되므로 private로 지정합니다.

ItemAdapter.kt

import com.example.affirmations.model.Affirmation

class ItemAdapter(private val dataset: List<Affirmation>) {

}

ItemAdapter에는 문자열 리소스를 확인하는 방법에 관한 정보가 필요합니다. 이러한 정보와 기타 앱 관련 정보는 ItemAdapter 인스턴스에 전달할 수 있는 Context 객체 인스턴스에 저장됩니다.

  1. ItemAdapter 생성자에 context라는 val(Context 유형)인 매개변수를 추가합니다. 생성자의 첫 번째 매개변수로 지정합니다.
class ItemAdapter(private val context: Context, private val dataset: List<Affirmation>) {

}

ViewHolder 만들기

RecyclerView는 항목 뷰와 직접 상호작용하지 않는 대신 ViewHolders를 처리합니다. ViewHolderRecyclerView의 단일 목록 항목 뷰를 나타내며 가능한 경우 재사용할 수 있습니다. ViewHolder 인스턴스는 목록 항목 레이아웃 안에 개별 뷰의 참조를 보유합니다(따라서 이름이 '뷰 홀더'임). 이렇게 하면 새로운 데이터로 목록 항목 뷰를 더 쉽게 업데이트할 수 있습니다. 뷰 홀더는 RecyclerView가 화면에서 뷰를 효율적으로 이동하기 위해 사용하는 정보도 추가합니다.

  1. ItemAdapter 클래스 내부에서 ItemAdapter의 닫는 중괄호 앞에 ItemViewHolder 클래스를 만듭니다.
class ItemAdapter(private val context: Context, private val dataset: List<Affirmation>) {

    class ItemViewHolder()
}
  • 다른 클래스 내부에 클래스를 정의하는 것을 중첩 클래스 생성이라고 합니다.
  • ItemAdapterItemViewHolder를 사용하므로 이 뷰 홀더를 ItemAdapter 내부에 생성하면 중첩 관계가 표시됩니다. 필수는 아니지만 이렇게 하면 다른 개발자가 프로그램의 구조를 이해하는 데 도움이 됩니다.
  1. View 유형의 private val viewItemViewHolder 클래스 생성자에 매개변수로 추가합니다.
  2. ItemViewHolderRecyclerView. ViewHolder의 서브클래스로 만듭니다. 그리고 view 매개변수를 슈퍼클래스 생성자에 전달합니다.
  3. ItemViewHolder 내부에서 TextView 유형인 val 속성 textView를 정의합니다. list_item.xml에서 정의한 ID item_title을 사용하여 뷰를 할당합니다.
class ItemAdapter(private val context: Context, private val dataset: List<Affirmation>) {

    class ItemViewHolder(private val view: View) : RecyclerView.ViewHolder(view) {
        val textView: TextView = view.findViewById(R.id.item_title)
    }
}

어댑터 메서드 재정의하기

  1. 추상 클래스 RecyclerView.Adapter에서 ItemAdapter를 확장하는 코드를 추가합니다. 꺾쇠 괄호 안에 뷰 홀더 유형으로 ItemAdapter.ItemViewHolder를 지정합니다.
class ItemAdapter(
    private val context: Context,
    private val dataset: List<Affirmation>
) : RecyclerView.Adapter<ItemAdapter.ItemViewHolder>() {

    class ItemViewHolder(private val view: View) : RecyclerView.ViewHolder(view) {
        val textView: TextView = view.findViewById(R.id.item_title)
    }
}

RecyclerView.Adapter에서 추상 메서드를 구현해야 하기 때문에 오류가 표시됩니다.

  1. ItemAdapter에 커서를 놓고 Command+I(Windows의 경우 Control+I)를 누릅니다. 그러면 구현해야 하는 메서드의 목록(getItemCount(), onCreateViewHolder(), onBindViewHolder())을 확인할 수 있습니다.

7a8a383a8633094b.png

  1. Shift+클릭을 사용하여 세 함수를 모두 선택하고 OK를 클릭합니다.

이렇게 하면 아래와 같이 세 메서드에 관해 올바른 매개변수가 포함된 스텁이 생성됩니다.

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ItemViewHolder {
    TODO("Not yet implemented")
}

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

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

오류가 더 이상 표시되지 않습니다. 다음으로, 앱에 적합한 작업을 수행하도록 이러한 메서드를 구현해야 합니다.

getItemCount() 구현하기

getItemCount() 메서드는 데이터 세트의 크기를 반환해야 합니다. 앱의 데이터는 ItemAdapter 생성자에 전달하는 dataset 속성에 있으며, size를 사용해 데이터의 크기를 가져올 수 있습니다.

  1. getItemCount()를 다음으로 바꿉니다.
override fun getItemCount() = dataset.size

위 방법은 다음을 더 간결하게 작성한 것입니다.

override fun getItemCount(): Int {
    return dataset.size
}

onCreateViewHolder() 구현하기

onCreateViewHolder() 메서드는 RecyclerView의 새 뷰 홀더를 만들기 위해 레이아웃 관리자가 호출합니다(재사용할 수 있는 기존 뷰 홀더가 없는 경우). 뷰 홀더는 단일 목록 항목 뷰를 나타냅니다.

onCreateViewHolder() 메서드는 두 매개변수를 사용하며 새 ViewHolder를 반환합니다.

  • parent 매개변수: 새 목록 항목 뷰가 하위 요소로 사용되어 연결되는 뷰 그룹입니다. 상위 요소는 RecyclerView입니다.
  • viewType 매개변수: 동일한 RecyclerView에 항목 뷰 유형이 여러 개 있을 때 중요해집니다. RecyclerView 내에 다른 목록 항목 레이아웃이 표시된다면 다른 항목 뷰 유형이 있는 것입니다. 동일한 항목 뷰 유형을 가진 뷰만 재활용할 수 있습니다. 이 경우에는 목록 항목 레이아웃이 하나만 있고 항목 뷰 유형이 하나이므로 이 매개변수에 관해 걱정할 필요가 없습니다.
  1. onCreateViewHolder() 메서드에서 제공된 컨텍스트(parentcontext)에서 LayoutInflater 인스턴스를 가져옵니다. 레이아웃 인플레이터는 XML 레이아웃을 뷰 객체의 계층 구조로 확장하는 방법을 알고 있습니다.
val adapterLayout = LayoutInflater.from(parent.context)
  1. 가져온 LayoutInflater 객체 인스턴스에 마침표를 추가하고 그 뒤에 다른 메서드 호출을 배치하여 실제 목록 항목 뷰를 확장합니다. XML 레이아웃 리소스 ID R.layout.list_itemparent 뷰 그룹을 전달합니다. 세 번째 부울 인수는 attachToRoot입니다. 이 인수는 false여야 합니다. 왜냐하면 적절한 때가 되면 RecyclerView가 이 항목을 뷰 계층 구조에 추가하기 때문입니다.
val adapterLayout = LayoutInflater.from(parent.context)
       .inflate(R.layout.list_item, parent, false)

이제 adapterLayout은 목록 항목 뷰의 참조를 보유합니다(나중에 이 참조에서

TextView 같은 하위 뷰를 찾을 수 있음).

  1. onCreateViewHolder()에서 루트 뷰가 adapterLayout인 새 ItemViewHolder 인스턴스를 반환합니다.
return ItemViewHolder(adapterLayout)

지금까지 onCreateViewHolder()의 코드는 다음과 같습니다.

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

    return ItemViewHolder(adapterLayout)
}

onBindViewHolder() 구현하기

마지막으로 재정의해야 하는 메서드는 onBindViewHolder()입니다. 이 메서드는 목록 항목 뷰의 콘텐츠를 바꾸기 위해 레이아웃 관리자가 호출합니다.

onBindViewHolder() 메서드에는 두 매개변수가 있으며, 하나는 이전에 onCreateViewHolder() 메서드에서 생성된 ItemViewHolder이고 다른 하나는 목록에서 현재 항목 position을 나타내는 int입니다. 이 메서드에서는 위치를 기반으로 데이터 세트에서 올바른 Affirmation 객체를 찾습니다.

  1. onBindViewHolder() 내부에 val item을 만들고 dataset의 지정된 position에 항목을 가져옵니다.
val item = dataset[position]

마지막으로 이 항목의 올바른 데이터를 반영하도록 뷰 홀더가 참조하는 모든 뷰를 업데이트해야 합니다. 이 경우에는 뷰가 하나만 있습니다(ItemViewHolder 내의 TextView). 이 항목의 Affirmation 문자열을 표시하도록 TextView의 텍스트를 설정합니다.

  1. Affirmation 객체 인스턴스를 사용하면 item.stringResourceId를 호출하여 상응하는 문자열 리소스 ID를 찾을 수 있습니다. 하지만 이 ID는 정수이며, 실제 문자열 값에 관한 매핑을 찾아야 합니다.

Android 프레임워크에서는 문자열 리소스 ID로 getString()을 호출할 수 있습니다. 그러면 연결된 문자열 값이 반환됩니다. getString()Resources 클래스의 메서드이며, context를 통해 Resources 클래스의 인스턴스를 가져올 수 있습니다.

다시 말해서, context.resources.getString()을 호출하고 문자열 리소스 ID를 전달할 수 있습니다. 결과 문자열을 holder ItemViewHolder에서 textViewtext로 설정할 수 있습니다. 간단히 말해, 이 코드 줄은 뷰 홀더를 업데이트하여 확인 문자열을 표시하도록 합니다.

holder.textView.text = context.resources.getString(item.stringResourceId)

완성된 onBindViewHolder() 메서드는 다음과 같습니다.

override fun onBindViewHolder(holder: ItemViewHolder, position: Int) {
    val item = dataset[position]
    holder.textView.text =  context.resources.getString(item.stringResourceId)
}

다음은 완성된 어댑터 코드입니다.

ItemAdapter.kt

package com.example.affirmations.adapter

import android.content.Context
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import com.example.affirmations.R
import com.example.affirmations.model.Affirmation

/**
 * Adapter for the [RecyclerView] in [MainActivity]. Displays [Affirmation] data object.
 */
class ItemAdapter(
    private val context: Context,
    private val dataset: List<Affirmation>
) : RecyclerView.Adapter<ItemAdapter.ItemViewHolder>() {

    // Provide a reference to the views for each data item
    // Complex data items may need more than one view per item, and
    // you provide access to all the views for a data item in a view holder.
    // Each data item is just an Affirmation object.
    class ItemViewHolder(private val view: View) : RecyclerView.ViewHolder(view) {
        val textView: TextView = view.findViewById(R.id.item_title)
    }

    /**
     * Create new views (invoked by the layout manager)
     */
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ItemViewHolder {
        // create a new view
        val adapterLayout = LayoutInflater.from(parent.context)
            .inflate(R.layout.list_item, parent, false)

        return ItemViewHolder(adapterLayout)
    }

    /**
     * Replace the contents of a view (invoked by the layout manager)
     */
    override fun onBindViewHolder(holder: ItemViewHolder, position: Int) {
        val item = dataset[position]
        holder.textView.text = context.resources.getString(item.stringResourceId)
    }

    /**
     * Return the size of your dataset (invoked by the layout manager)
     */
    override fun getItemCount() = dataset.size
}

이제 ItemAdapter를 구현했으므로 이 어댑터를 사용하도록 RecyclerView에 알려야 합니다.

RecyclerView를 사용하도록 MainActivity 수정하기

완료하려면 Datasource 클래스와 ItemAdapter 클래스를 사용하여 RecyclerView에 항목을 만들고 표시합니다. MainActivity에서 하면 됩니다.

  1. MainActivity.kt를 엽니다.
  2. MainActivity,에서 onCreate() 메서드로 이동합니다. setContentView(R.layout.activity_main). 호출 뒤에 다음 단계에서 설명하는 새 코드를 삽입합니다.
  3. Datasource 인스턴스를 만들고 이 인스턴스에서 loadAffirmations() 메서드를 호출합니다. 반환된 확인 목록을 myDataset라는 val에 저장합니다.
val myDataset = Datasource().loadAffirmations()
  1. recyclerView라는 변수를 만들고 findViewById()를 사용하여 레이아웃 내에서 RecyclerView 참조를 찾습니다.
val recyclerView = findViewById<RecyclerView>(R.id.recycler_view)
  1. 만든 ItemAdapter 클래스를 사용하도록 recyclerView에 알리려면 새 ItemAdapter 인스턴스를 만듭니다. ItemAdapter에는 두 매개변수, 즉 이 활동의 컨텍스트(this)와 myDataset의 확인 값이 필요합니다.
  2. ItemAdapter 객체를 recyclerViewadapter 속성에 할당합니다.
recyclerView.adapter = ItemAdapter(this, myDataset)
  1. 활동 레이아웃에서 RecyclerView의 레이아웃 크기가 고정되어 있으므로 RecyclerViewsetHasFixedSize 매개변수를 true로 설정할 수 있습니다. 성능을 개선하기 위해서만 이 설정이 필요합니다. 콘텐츠를 변경해도 RecyclerView의 레이아웃 크기가 변경되지 않는다는 것을 아는 경우 이 설정을 사용합니다.
recyclerView.setHasFixedSize(true)
  1. 완료되면 MainActivity의 코드는 다음과 유사합니다.

MainActivity.kt

package com.example.affirmations

import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.recyclerview.widget.RecyclerView
import com.example.affirmations.adapter.ItemAdapter
import com.example.affirmations.data.Datasource

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        // Initialize data.
        val myDataset = Datasource().loadAffirmations()

        val recyclerView = findViewById<RecyclerView>(R.id.recycler_view)
        recyclerView.adapter = ItemAdapter(this, myDataset)

        // Use this setting to improve performance if you know that changes
        // in content do not change the layout size of the RecyclerView
        recyclerView.setHasFixedSize(true)
    }
}
  1. 앱을 실행합니다. 화면에 확인 문자열 목록이 표시됩니다.

427c10d4f29d769d.png

축하합니다. RecyclerView와 맞춤 어댑터를 사용하여 데이터 목록을 표시하는 앱을 만들었습니다. 잠시 시간을 내서 내가 만든 코드를 살펴보고 여러 부분이 함께 작동하는 방식을 파악하세요.

이 앱은 확인을 표시하는 데 필요한 모든 부분을 포함하고 있지만 아직 프로덕션용으로는 준비되지 않았습니다. UI에 개선할 점이 있습니다. 다음 Codelab에서는 코드를 개선하고, 앱에 이미지를 추가하는 방법을 알아보고, UI를 깔끔하게 만듭니다.

이 Codelab의 솔루션 코드는 아래에 나온 프로젝트와 모듈에 있습니다. 일부 Kotlin 파일은 파일 시작 부분의 package 문에서 나타내는 바와 같이 서로 다른 패키지에 있습니다.

res/values/strings.xml

<resources>
    <string name="app_name">Affirmations</string>
    <string name="affirmation1">I am strong.</string>
    <string name="affirmation2">I believe in myself.</string>
    <string name="affirmation3">Each day is a new opportunity to grow and be a better version of myself.</string>
    <string name="affirmation4">Every challenge in my life is an opportunity to learn from.</string>
    <string name="affirmation5">I have so much to be grateful for.</string>
    <string name="affirmation6">Good things are always coming into my life.</string>
    <string name="affirmation7">New opportunities await me at every turn.</string>
    <string name="affirmation8">I have the courage to follow my heart.</string>
    <string name="affirmation9">Things will unfold at precisely the right time.</string>
    <string name="affirmation10">I will be present in all the moments that this day brings.</string>
</resources>

affirmations/data/Datasource.kt

package com.example.affirmations.data

import com.example.affirmations.R
import com.example.affirmations.model.Affirmation

class Datasource {

    fun loadAffirmations(): List<Affirmation> {
        return listOf<Affirmation>(
            Affirmation(R.string.affirmation1),
            Affirmation(R.string.affirmation2),
            Affirmation(R.string.affirmation3),
            Affirmation(R.string.affirmation4),
            Affirmation(R.string.affirmation5),
            Affirmation(R.string.affirmation6),
            Affirmation(R.string.affirmation7),
            Affirmation(R.string.affirmation8),
            Affirmation(R.string.affirmation9),
            Affirmation(R.string.affirmation10)
        )
    }
}

affirmations/model/Affirmation.kt

package com.example.affirmations.model

data class Affirmation(val stringResourceId: Int)

affirmations/MainActivty.kt

package com.example.affirmations

import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.recyclerview.widget.RecyclerView
import com.example.affirmations.adapter.ItemAdapter
import com.example.affirmations.data.Datasource

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        // Initialize data.
        val myDataset = Datasource().loadAffirmations()

        val recyclerView = findViewById<RecyclerView>(R.id.recycler_view)
        recyclerView.adapter = ItemAdapter(this, myDataset)

        // Use this setting to improve performance if you know that changes
        // in content do not change the layout size of the RecyclerView
        recyclerView.setHasFixedSize(true)
    }
}

affirmations/adapter/ItemAdapter.kt

package com.example.affirmations.adapter

import android.content.Context
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import com.example.affirmations.R
import com.example.affirmations.model.Affirmation

/**
 * Adapter for the [RecyclerView] in [MainActivity]. Displays [Affirmation] data object.
 */
class ItemAdapter(
    private val context: Context,
    private val dataset: List<Affirmation>
) : RecyclerView.Adapter<ItemAdapter.ItemViewHolder>() {

    // Provide a reference to the views for each data item
    // Complex data items may need more than one view per item, and
    // you provide access to all the views for a data item in a view holder.
    // Each data item is just an Affirmation object.
    class ItemViewHolder(private val view: View) : RecyclerView.ViewHolder(view) {
        val textView: TextView = view.findViewById(R.id.item_title)
    }

    /**
     * Create new views (invoked by the layout manager)
     */
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ItemViewHolder {
        // create a new view
        val adapterLayout = LayoutInflater.from(parent.context)
            .inflate(R.layout.list_item, parent, false)

        return ItemViewHolder(adapterLayout)
    }

    /**
     * Replace the contents of a view (invoked by the layout manager)
     */
    override fun onBindViewHolder(holder: ItemViewHolder, position: Int) {
        val item = dataset[position]
        holder.textView.text = context.resources.getString(item.stringResourceId)
    }

    /**
     * Return the size of your dataset (invoked by the layout manager)
     */
    override fun getItemCount() = dataset.size
}

src/main/res/layout/activty_main.xml

<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=".MainActivity">

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

</FrameLayout>

src/main/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" />
  • RecyclerView 위젯을 사용하여 데이터 목록을 표시할 수 있습니다.
  • RecyclerView는 어댑터 패턴을 사용하여 데이터를 조정하고 표시합니다.
  • ViewHolderRecyclerView의 뷰를 만들고 보유합니다.
  • RecyclerView는 내장된 LayoutManagers와 함께 제공됩니다. RecyclerView는 항목을 배치하는 방식을 LayoutManagers에 위임합니다.

어댑터를 구현하려면 다음을 따르세요.

  • 어댑터의 새 클래스(예: ItemAdapter)를 만듭니다.
  • 단일 목록 항목 뷰를 나타내는 맞춤 ViewHolder 클래스를 만듭니다. RecyclerView.ViewHolder 클래스에서 확장합니다.
  • ItemAdapter 클래스를 수정하여 RecyclerView.Adapter 클래스에서 확장합니다(맞춤 ViewHolder 클래스 사용).
  • 어댑터 내에서 getItemsCount(), onCreateViewHolder(), onBindViewHolder() 메서드를 구현합니다.