Android Kotlin 기초 07.5: RecyclerView의 헤더

이 Codelab은 Android Kotlin 기초 교육 과정의 일부입니다. Codelab을 순서대로 진행한다면 이 과정을 통해 최대한의 가치를 얻을 수 있을 것입니다. 모든 과정 Codelab은 Android Kotlin 기초 Codelab 방문 페이지에 나열되어 있습니다.

소개

이 Codelab에서는 RecyclerView에 표시되는 목록의 너비에 걸친 헤더를 추가하는 방법을 알아봅니다. 이전 Codelab을 통해 수면 추적기 앱을 빌드했습니다.

기본 요건

  • 활동, 프래그먼트, 뷰를 사용하여 기본 사용자 인터페이스를 빌드하는 방법
  • 프래그먼트 간에 이동하는 방법과 safeArgs를 사용하여 프래그먼트 간에 데이터를 전달하는 방법
  • 모델 보기, 모델 팩토리, 변환, LiveData 및 그 관찰자를 봅니다.
  • Room 데이터베이스를 만들고 DAO를 만들고 항목을 정의하는 방법
  • 데이터베이스 상호작용 및 기타 장기 실행 작업에 코루틴을 사용하는 방법
  • Adapter, ViewHolder, 항목 레이아웃으로 기본 RecyclerView를 구현하는 방법
  • RecyclerView의 데이터 결합을 구현하는 방법
  • 결합 어댑터를 만들고 사용하여 데이터를 변환하는 방법
  • GridLayoutManager 사용 방법
  • RecyclerView.에서 항목 클릭을 캡처하고 처리하는 방법

학습할 내용

  • RecyclerView와 함께 ViewHolder를 두 개 이상 사용하여 다른 레이아웃의 항목을 추가하는 방법 특히 두 번째 ViewHolder를 사용하여 RecyclerView에 표시된 항목 위에 헤더를 추가하는 방법을 알아야 합니다.

실습할 내용

  • 이 시리즈의 이전 Codelab을 통해 TrackMySleepQuality 앱을 빌드합니다.
  • RecyclerView에 표시되는 수면 밤 위에 화면 너비에 걸쳐되는 헤더를 추가합니다.

시작하는 수면 추적기 앱에는 아래 그림과 같이 프래그먼트로 표시되는 세 개의 화면이 있습니다.

왼쪽에 표시된 첫 번째 화면에는 추적을 시작하고 중지하는 버튼이 있습니다. 화면에 사용자의 일부 수면 데이터가 표시됩니다. 지우기 버튼은 앱에서 사용자에 대해 수집한 모든 데이터를 완전히 삭제합니다. 가운데 표시된 두 번째 화면은 수면의 질 등급을 선택하기 위한 것입니다. 세 번째 화면은 사용자가 그리드의 항목을 탭할 때 열리는 세부정보 뷰입니다.

이 앱은 UI 컨트롤러, 뷰 모델 및 LiveData, Room 데이터베이스를 갖춘 간소화된 아키텍처를 사용하여 수면 데이터를 유지합니다.

이 Codelab에서는 표시된 항목 그리드에 헤더를 추가합니다. 최종 기본 화면은 다음과 같이 표시됩니다.

이 Codelab에서는 RecyclerView에 다양한 레이아웃을 사용하는 항목을 포함하는 일반적인 원칙을 설명합니다. 한 가지 일반적인 예는 목록 또는 그리드에 헤더를 포함하는 것입니다. 목록에는 항목 콘텐츠를 설명하는 헤더가 하나만 있을 수 있습니다. 목록에 여러 헤더를 사용하여 단일 목록에서 항목을 그룹화하고 구분할 수도 있습니다.

RecyclerView는 데이터나 각 항목의 레이아웃 유형을 전혀 모릅니다. LayoutManager는 항목을 화면에 정렬하지만 어댑터는 표시할 데이터를 조정하고 뷰 홀더를 RecyclerView에 전달합니다. 따라서 코드를 추가하여 어댑터에 헤더를 만듭니다.

헤더를 추가하는 두 가지 방법

RecyclerView에서 목록의 모든 항목은 0부터 시작되는 색인 번호에 상응합니다. 예를 들면 다음과 같습니다.

[실제 데이터] -> [어댑터 뷰]

[0: SleepNight] -> [0: SleepNight]

[1: SleepNight] -> [1: SleepNight]

[2: SleepNight] -> [2: SleepNight]

목록에 헤더를 추가하는 한 가지 방법은 헤더를 표시해야 하는 색인을 확인하여 다른 ViewHolder를 사용하도록 어댑터를 수정하는 것입니다. Adapter는 헤더를 추적하는 역할을 합니다. 예를 들어 표 상단에 헤더를 표시하려면 색인이 생성되지 않은 항목을 배치하고 헤더에 0의 다른 ViewHolder를 반환해야 합니다. 그러면 다른 모든 항목은 아래와 같이 헤더 오프셋으로 매핑됩니다.

[실제 데이터] -> [어댑터 뷰]

[0: 헤더]

[0: SleepNight] -> [1: SleepNight]

[1: SleepNight] -> [2: SleepNight]

[2: SleepNight] -> [3: SleepNight]

헤더를 추가하는 또 다른 방법은 데이터 그리드의 지원 데이터 세트를 수정하는 것입니다. 표시해야 하는 모든 데이터는 목록에 저장되므로 헤더를 나타내는 항목을 포함하도록 목록을 수정할 수 있습니다. 이해가 좀 더 간단하지만 객체를 설계하는 방법을 생각해봐야 하므로 여러 항목 유형을 단일 목록으로 결합할 수 있습니다. 이런 방식으로 구현된 어댑터는 어댑터에 전달된 항목을 표시합니다. 따라서 게재순위 0의 항목은 헤더이고 위치 1의 항목은 SleepNight이며 화면의 항목에 직접 매핑됩니다.

[실제 데이터] -> [어댑터 뷰]

[0: 헤더] -> [0: 헤더]

[1: SleepNight] -> [1: SleepNight]

[2: SleepNight] -> [2: SleepNight]

[3: SleepNight] -> [3: SleepNight]

각 방법마다 장단점이 있습니다. 데이터 세트를 변경해도 나머지 어댑터 코드는 많이 변경되지 않으며 데이터 목록을 조작하여 헤더 로직을 추가할 수 있습니다. 반면, 헤더의 색인을 확인하여 다른 ViewHolder를 사용하면 헤더의 레이아웃에서 더 많은 자유를 얻게 됩니다. 또한, 어댑터는 지원 데이터를 수정하지 않고도 어댑터가 데이터에 맞게 조정되는 방식을 처리할 수 있습니다.

이 Codelab에서는 RecyclerView를 업데이트하여 목록 시작 부분에 헤더를 표시합니다. 이 경우 앱에서는 헤더에 데이터 항목과 다른 ViewHolder를 사용합니다. 앱은 목록의 색인을 확인하여 사용할 ViewHolder를 결정합니다.

1단계: DataItem 클래스 만들기

항목 유형을 추상화하고 어댑터가 "items"를 처리하도록 하려면 SleepNight 또는 Header를 나타내는 데이터 홀더 클래스를 만들면 됩니다. 그러면 데이터 세트 항목이 데이터 홀더 항목 목록이 됩니다.

GitHub에서 시작 앱을 가져오거나 이전 Codelab에서 빌드한 SleepTracker 앱을 계속 사용할 수 있습니다.

  1. GitHub에서 RecyclerViewHeaders-Starter 코드를 다운로드합니다. RecyclerViewHeaders-Starter 디렉터리에는 이 Codelab에 필요한 SleepTracker 앱의 시작 버전이 포함되어 있습니다. 원한다면 이전 Codelab을 통해 완성된 앱을 계속 사용할 수도 있습니다.
  2. SleepNightAdapter.kt를 엽니다.
  3. SleepNightListener 클래스 아래에서 최상위 수준의 데이터 항목을 나타내는 DataItem이라는 sealed 클래스를 정의합니다.

    sealed 클래스는 닫힌 유형을 정의합니다. 즉, DataItem의 모든 서브클래스가 이 파일에 정의되어야 합니다. 따라서 서브클래스의 수는 컴파일러에 알려져 있습니다. 코드의 다른 부분에서 어댑터를 손상시킬 수 있는 새로운 유형의 DataItem를 정의할 수는 없습니다.
sealed class DataItem {

 }
  1. DataItem 클래스 본문 내에서 서로 다른 데이터 항목 유형을 나타내는 두 개의 클래스를 정의합니다. 첫 번째는 SleepNight의 래퍼인 SleepNightItem로, sleepNight라는 단일 값을 사용합니다. 봉인 클래스의 일부로 만들려면 DataItem를 확장합니다.
data class SleepNightItem(val sleepNight: SleepNight): DataItem()
  1. 두 번째 클래스는 Header이며 헤더를 나타냅니다. 헤더에 실제 데이터가 없으므로 헤더를 object로 선언할 수 있습니다. 즉, Header 인스턴스는 하나만 존재합니다. 다시 DataItem를 확장합니다.
object Header: DataItem()
  1. DataItem 내 클래스 수준에서 id라는 abstract Long 속성을 정의합니다. 어댑터가 DiffUtil를 사용하여 항목의 변경 여부와 방법을 결정할 때 DiffItemCallback은 각 항목의 ID를 알아야 합니다. SleepNightItemHeader이 추상 속성 id을 재정의해야 하기 때문에 오류가 표시됩니다.
abstract val id: Long
  1. SleepNightItem에서 id를 재정의하여 nightId를 반환합니다.
override val id = sleepNight.nightId
  1. Header에서 id을 재정의하여 매우 작은 숫자 (즉, -2의 63제곱)인 Long.MIN_VALUE를 반환합니다. 따라서 존재하는 nightId와 충돌하지 않습니다.
override val id = Long.MIN_VALUE
  1. 완성된 코드는 다음과 같이 표시되고 오류 없이 앱이 빌드됩니다.
sealed class DataItem {
    abstract val id: Long
    data class SleepNightItem(val sleepNight: SleepNight): DataItem()      {
        override val id = sleepNight.nightId
    }

    object Header: DataItem() {
        override val id = Long.MIN_VALUE
    }
}

2단계: 헤더의 ViewHolder 만들기

  1. TextView를 표시하는 header.xml이라는 새 레이아웃 리소스 파일에서 헤더의 레이아웃을 만듭니다. 흥미롭지 않은 코드가 있으므로 다음과 같습니다.
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/text"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:textAppearance="?android:attr/textAppearanceLarge"
    android:text="Sleep Results"
    android:padding="8dp" />
  1. "Sleep Results"을 문자열 리소스로 추출하여 이름을 header_text로 지정합니다.
<string name="header_text">Sleep Results</string>
  1. SleepNightAdapter.ktSleepNightAdapter 내에서 ViewHolder 클래스 위에 새 TextViewHolder 클래스를 만듭니다. 이 클래스는 textview.xml 레이아웃을 확장하고 TextViewHolder 인스턴스를 반환합니다. 이전에 완료했으므로 다음 코드는 다음과 같습니다. ViewR를 가져와야 합니다.
    class TextViewHolder(view: View): RecyclerView.ViewHolder(view) {
        companion object {
            fun from(parent: ViewGroup): TextViewHolder {
                val layoutInflater = LayoutInflater.from(parent.context)
                val view = layoutInflater.inflate(R.layout.header, parent, false)
                return TextViewHolder(view)
            }
        }
    }

3단계: SleepNightAdapter 업데이트

다음으로 SleepNightAdapter의 선언을 업데이트해야 합니다. 한 가지 유형의 ViewHolder만 지원하는 대신 모든 유형의 뷰 홀더를 사용할 수 있어야 합니다.

항목 유형 정의

  1. SleepNightAdapter.kt의 최상위 수준 import 문과 SleepNightAdapter 위에서 뷰 유형에 상수를 두 개 정의합니다.

    RecyclerView은 뷰 홀더를 뷰 할당 항목에 올바르게 할당할 수 있도록 각 항목의 뷰 유형을 구분해야 합니다.
    private val ITEM_VIEW_TYPE_HEADER = 0
    private val ITEM_VIEW_TYPE_ITEM = 1
  1. SleepNightAdapter 내부에서 현재 항목의 유형에 따라 오른쪽 헤더 또는 항목 상수를 반환하도록 getItemViewType()을 재정의하는 함수를 만듭니다.
override fun getItemViewType(position: Int): Int {
        return when (getItem(position)) {
            is DataItem.Header -> ITEM_VIEW_TYPE_HEADER
            is DataItem.SleepNightItem -> ITEM_VIEW_TYPE_ITEM
        }
    }

SleepNightAdapter 정의 업데이트

  1. SleepNightAdapter 정의에서 ListAdapter의 첫 번째 인수를 SleepNight에서 DataItem로 업데이트합니다.
  2. SleepNightAdapter 정의에서 ListAdapter의 두 번째 일반 인수를 SleepNightAdapter.ViewHolder에서 RecyclerView.ViewHolder로 변경합니다. 필요한 업데이트에 대한 오류가 표시되고 클래스 헤더는 아래와 같습니다.
class SleepNightAdapter(val clickListener: SleepNightListener):
       ListAdapter<DataItem, RecyclerView.ViewHolder>(SleepNightDiffCallback()) {

onCreateViewHolder() 업데이트하기

  1. onCreateViewHolder()의 서명을 변경하여 RecyclerView.ViewHolder를 반환합니다.
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder
  1. onCreateViewHolder() 메서드의 구현을 펼쳐서 각 항목 유형에 적절한 뷰 홀더를 테스트하고 반환합니다. 업데이트된 메서드는 아래 코드와 같이 표시됩니다.
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
        return when (viewType) {
            ITEM_VIEW_TYPE_HEADER -> TextViewHolder.from(parent)
            ITEM_VIEW_TYPE_ITEM -> ViewHolder.from(parent)
            else -> throw ClassCastException("Unknown viewType ${viewType}")
        }
    }

onBindViewHolder() 업데이트하기

  1. onBindViewHolder()의 매개변수 유형을 ViewHolder에서 RecyclerView.ViewHolder로 변경합니다.
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int)
  1. 홀더가 ViewHolder인 경우에만 뷰 홀더에 데이터를 할당하는 조건을 추가합니다.
        when (holder) {
            is ViewHolder -> {...}
  1. getItem()에서 반환된 객체 유형을 DataItem.SleepNightItem로 변환합니다. 완성된 onBindViewHolder() 함수는 다음과 같습니다.
  override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
        when (holder) {
            is ViewHolder -> {
                val nightItem = getItem(position) as DataItem.SleepNightItem
                holder.bind(nightItem.sleepNight, clickListener)
            }
        }
    }

diffUtil 콜백 업데이트

  1. SleepNight 대신 새 DataItem 클래스를 사용하도록 SleepNightDiffCallback의 메서드를 변경합니다. 아래 코드와 같이 린트 경고를 표시하지 않습니다.
class SleepNightDiffCallback : DiffUtil.ItemCallback<DataItem>() {
    override fun areItemsTheSame(oldItem: DataItem, newItem: DataItem): Boolean {
        return oldItem.id == newItem.id
    }
    @SuppressLint("DiffUtilEquals")
    override fun areContentsTheSame(oldItem: DataItem, newItem: DataItem): Boolean {
        return oldItem == newItem
    }
}

헤더 추가 및 제출

  1. SleepNightAdapter 내부의 onCreateViewHolder() 아래에서 아래와 같이 함수 addHeaderAndSubmitList()를 정의합니다. 이 함수는 SleepNight 목록을 사용합니다. ListAdapter에서 제공하는 submitList()를 사용하여 목록을 제출하는 대신 이 함수를 사용하여 헤더를 추가한 다음 목록을 제출합니다.
fun addHeaderAndSubmitList(list: List<SleepNight>?) {}
  1. addHeaderAndSubmitList() 내에서 전달된 목록이 null이면 헤더만 반환하고 그렇지 않으면 헤더를 목록 헤드에 연결한 다음 목록을 제출합니다.
val items = when (list) {
                null -> listOf(DataItem.Header)
                else -> listOf(DataItem.Header) + list.map { DataItem.SleepNightItem(it) }
            }
submitList(items)
  1. SleepTrackerFragment.kt를 열고 submitList() 호출을 addHeaderAndSubmitList()로 변경합니다.
  1. 앱을 실행하고 헤더가 수면 항목 목록의 첫 번째 항목으로 표시되는지 확인합니다.

이 앱에 대해 수정해야 할 두 가지가 있습니다. 하나는 표시되고 다른 하나는 표시되지 않습니다.

  • 헤더가 왼쪽 상단에 표시되고 쉽게 구분할 수 없습니다.
  • 헤더가 하나뿐인 간단한 목록에는 중요하지 않지만 UI 스레드의 addHeaderAndSubmitList()에서 조작을 나열해서는 안 됩니다. 수백 개의 항목, 여러 개의 헤더, 논리가 포함된 목록을 추가하여 어디에 삽입해야 할지 결정한다고 가정해 보겠습니다. 이 작업은 코루틴에 속합니다.

코루틴을 사용하도록 addHeaderAndSubmitList()를 변경합니다.

  1. SleepNightAdapter 클래스 내부의 최상위 수준에서 Dispatchers.Default를 사용하여 CoroutineScope를 정의합니다.
private val adapterScope = CoroutineScope(Dispatchers.Default)
  1. addHeaderAndSubmitList()adapterScope에서 코루틴을 실행하여 목록을 조작합니다. 그런 다음 Dispatchers.Main 컨텍스트로 전환하여 아래 코드와 같이 목록을 제출합니다.
 fun addHeaderAndSubmitList(list: List<SleepNight>?) {
        adapterScope.launch {
            val items = when (list) {
                null -> listOf(DataItem.Header)
                else -> listOf(DataItem.Header) + list.map { DataItem.SleepNightItem(it) }
            }
            withContext(Dispatchers.Main) {
                submitList(items)
            }
        }
    }
  1. 코드가 빌드되고 실행되어야 별다른 차이가 없습니다.

현재 헤더는 그리드의 다른 항목과 너비가 같아서 가로 또는 세로로 한 스팬만 차지합니다. 전체 그리드는 한 스팬 너비의 항목 세 개를 가로로 고정하므로 헤더는 스팬을 세 번 사용해야 합니다.

헤더 너비를 수정하려면 GridLayoutManager를 모든 열에 스팬해야 하는 시기를 지정해야 합니다. 이렇게 하려면 GridLayoutManagerSpanSizeLookup를 구성하면 됩니다. 이는 GridLayoutManager가 목록의 각 항목에 사용할 스팬을 결정하는 데 사용하는 구성 객체입니다.

  1. SleepTrackerFragment.kt를 엽니다.
  2. onCreateView() 끝부분에 있는 manager를 정의하는 코드를 찾습니다.
val manager = GridLayoutManager(activity, 3)
  1. manager 아래에 다음과 같이 manager.spanSizeLookup를 정의합니다. setSpanSizeLookup이 람다를 사용하지 않으므로 object를 만들어야 합니다. Kotlin에서 object을 만들려면 object : classname(이 경우에는 GridLayoutManager.SpanSizeLookup)를 입력합니다.
manager.spanSizeLookup = object : GridLayoutManager.SpanSizeLookup() {
}
  1. 생성자를 호출하는 컴파일러 오류가 발생할 수 있습니다. 그러면 Option+Enter (Mac) 또는 Alt+Enter (Windows)로 인텐트 메뉴를 열어 생성자 호출을 적용합니다.
  1. 그러면 메서드 재정의가 필요하다는 object 메시지가 표시됩니다. object에 커서를 놓고 Option+Enter (Mac) 또는 Alt+Enter (Windows)를 눌러 인텐트 메뉴를 연 다음 getSpanSize() 메서드를 재정의합니다.
  1. getSpanSize()의 본문에서 각 위치의 오른쪽 스팬 크기를 반환합니다. 게재순위 0의 스팬 크기는 3이고 다른 위치의 스팬 크기는 1입니다. 완성된 코드는 아래 코드와 같이 표시됩니다.
    manager.spanSizeLookup = object : GridLayoutManager.SpanSizeLookup() {
            override fun getSpanSize(position: Int) =  when (position) {
                0 -> 3
                else -> 1
            }
        }
  1. 헤더의 모양을 개선하려면 header.xml을 열고 이 코드를 레이아웃 파일 header.xml에 추가합니다.
android:textColor="@color/white_text_color"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="16dp"
android:background="@color/colorAccent"
  1. 앱을 실행합니다. 아래의 스크린샷과 같이 표시됩니다.

수고하셨습니다. 이제 작업이 끝났습니다.

Android 스튜디오 프로젝트: RecyclerViewHeaders

  • 헤더는 일반적으로 목록의 너비를 확장하며 제목이나 구분자 역할을 하는 항목입니다. 목록에는 상품 콘텐츠를 설명하는 단일 헤더 또는 항목을 그룹화하고 항목을 서로 분리하는 여러 헤더가 있을 수 있습니다.
  • RecyclerView는 여러 뷰 홀더를 사용하여 이기종 항목(예: 헤더 및 목록 항목)을 수용할 수 있습니다.
  • 헤더를 추가하는 한 가지 방법은 헤더를 표시해야 하는 색인을 확인하여 다른 ViewHolder를 사용하도록 어댑터를 수정하는 것입니다. Adapter는 헤더를 추적하는 역할을 합니다.
  • 헤더를 추가하는 또 다른 방법은 데이터 그리드의 지원 데이터 세트 (목록)를 수정하는 것입니다. 이 Codelab에서는 이 작업을 수행했습니다.

다음은 헤더를 추가하는 주요 단계입니다.

  • 헤더 또는 데이터를 보유할 수 있는 DataItem를 만들어 목록의 데이터를 추상화합니다.
  • 어댑터의 헤더 레이아웃이 있는 뷰 홀더를 만듭니다.
  • 모든 종류의 RecyclerView.ViewHolder를 사용하도록 어댑터 및 그 메서드를 업데이트합니다.
  • onCreateViewHolder()에서 데이터 항목의 올바른 뷰 홀더 유형을 반환합니다.
  • SleepNightDiffCallbackDataItem 클래스를 사용하도록 업데이트합니다.
  • 코루틴을 사용하여 데이터 세트에 헤더를 추가한 다음 submitList()를 호출하는 addHeaderAndSubmitList() 함수를 만듭니다.
  • GridLayoutManager.SpanSizeLookup()을 구현하여 헤더 너비가 3개인 너비만 만듭니다.

Udacity 과정:

Android 개발자 문서:

이 섹션에는 강사가 진행하는 과정의 일부로 이 Codelab을 통해 작업하는 학생들의 숙제 과제가 나와 있습니다. 강사는 다음을 처리합니다.

  • 필요한 경우 과제를 할당합니다.
  • 학생에게 과제 과제를 제출하는 방법을 알려주세요.
  • 과제 과제를 채점합니다.

강사는 이러한 추천을 원하는 만큼 사용할 수 있으며 다른 적절한 숙제를 할당해도 좋습니다.

이 Codelab을 직접 학습하고 있다면 언제든지 숙제를 통해 지식을 확인해 보세요.

답변

질문 1

ViewHolder에 관한 다음 설명 중 올바른 것은 무엇인가요?

▢ 어댑터는 여러 ViewHolder 클래스를 사용하여 헤더와 다양한 유형의 데이터를 보유할 수 있습니다.

▢ 데이터를 위한 뷰 홀더 및 헤더용 뷰 홀더가 하나만 있을 수 있음

▢ A RecyclerView는 여러 유형의 헤더를 지원하지만 데이터가 균일해야 합니다.

▢ 헤더를 추가할 때 올바른 위치에 헤더를 삽입하기 위해 RecyclerView의 서브클래스를 생성합니다.

질문 2

RecyclerView와 함께 코루틴을 사용해야 하는 경우 참인 문장을 모두 선택하세요.

▢ 아니요. RecyclerView은 UI 요소이므로 코루틴을 사용하면 안 됩니다.

▢ UI를 느리게 할 수 있는 장기 실행 작업에 코루틴을 사용합니다.

▢ 목록 조작에는 시간이 오래 걸릴 수 있으며 코루틴을 사용하여 항상 조작해야 합니다.

▢ 기본 스레드를 차단하지 않으려면 정지 함수와 함께 코루틴을 사용합니다.

질문 3

다음 중 ViewHolder를 두 개 이상 사용할 때 할 필요가 없는 것은 무엇인가요?

ViewHolder에서 필요에 따라 확장되는 레이아웃 파일을 여러 개 제공합니다.

onCreateViewHolder()에서 데이터 항목의 올바른 뷰 홀더 유형을 반환합니다.

onBindViewHolder()에서는 뷰 홀더가 데이터 항목의 올바른 뷰 홀더 유형인 경우에만 데이터를 바인딩합니다.

▢ 어댑터 클래스 서명을 일반화하여 모든 RecyclerView.ViewHolder를 허용합니다.

다음 강의 시작: 8.1 인터넷에서 데이터 가져오기

이 과정의 다른 Codelab 링크는 Android Kotlin 기초 Codelab 방문 페이지를 참고하세요.