이 Codelab은 Android Kotlin 기초 교육 과정의 일부입니다. Codelab을 순서대로 진행한다면 이 과정을 통해 최대한의 가치를 얻을 수 있을 것입니다. 모든 과정 Codelab은 Android Kotlin 기초 Codelab 방문 페이지에 나열되어 있습니다.
소개
이 Codelab에서는 RecyclerView
를 사용하여 항목 목록을 표시하는 방법을 알아봅니다. 이전 Codelab 시리즈에서 수면 추적기 앱을 빌드한 상태에서 권장 아키텍처와 함께 RecyclerView
를 사용하면 데이터를 더욱 다양한 방법으로 표시할 수 있습니다.
기본 요건
다음을 잘 알고 있어야 합니다.
- 활동, 프래그먼트, 뷰를 사용하여 기본 사용자 인터페이스 (UI) 빌드
- 프래그먼트 간 이동 및
safeArgs
을 사용하여 프래그먼트 간 데이터 전달 - 뷰 모델, 뷰 팩토리, 변환,
LiveData
및 그 관찰자를 사용합니다. Room
데이터베이스 만들기, DAO 만들기, 항목 정의- 데이터베이스 작업 및 기타 장기 실행 작업에 코루틴 사용
학습할 내용
Adapter
및ViewHolder
와 함께RecyclerView
를 사용하여 항목 목록을 표시하는 방법
실습할 내용
RecyclerView
를 사용하여 수면의 질 데이터를 표시하도록 이전 강의에서 TrackMySleepQuality 앱을 변경합니다.
이 Codelab에서는 수면의 질을 추적하는 앱의 RecyclerView
부분을 빌드합니다. 앱은 Room
데이터베이스를 사용하여 시간 경과에 따른 수면 데이터를 저장합니다.
시작 수면 추적기 앱에는 아래 그림과 같이 프래그먼트로 표시되는 두 개의 화면이 있습니다.
왼쪽에 표시된 첫 번째 화면에는 추적을 시작하고 중지하는 버튼이 있습니다. 이 화면에는 사용자의 모든 수면 데이터도 표시됩니다. 지우기 버튼은 앱에서 사용자에 대해 수집한 모든 데이터를 완전히 삭제합니다. 오른쪽에 표시된 두 번째 화면은 수면의 질 등급을 선택하기 위한 것입니다.
이 앱은 UI 컨트롤러, ViewModel
및 LiveData
가 있는 간소화된 아키텍처를 사용합니다. 또한 앱은 Room
데이터베이스를 사용하여 수면 데이터를 영구적으로 유지합니다.
첫 번째 화면에 표시되는 수면 시간 목록은 작동하지만 제대로 작동하지는 않습니다. 앱은 복잡한 형식 지정 도구를 사용하여 텍스트 뷰의 텍스트 문자열 및 품질에 관한 숫자를 생성합니다. 또한 이 설계는 확장되지 않습니다. 이 Codelab에서 이러한 문제를 모두 해결하면 최종 앱의 기능이 동일하며 기본 화면은 다음과 같습니다.
데이터 목록 또는 그리드 표시는 Android에서 가장 일반적인 UI 작업 중 하나입니다. 목록은 단순한 것부터 매우 복잡한 것까지 다양합니다. 텍스트 뷰 목록에는 쇼핑 목록과 같은 간단한 데이터가 표시될 수 있습니다. 주석이 달린 여행 목적지 목록과 같은 복잡한 목록은 사용자에게 헤더가 있는 스크롤 그리드 안에 여러 세부정보를 보여줄 수 있습니다.
이러한 모든 사용 사례를 지원하기 위해 Android는 RecyclerView
위젯을 제공합니다.
RecyclerView
의 가장 큰 장점은 대규모 목록에 매우 효율적이라는 점입니다.
- 기본적으로
RecyclerView
는 현재 화면에 표시된 항목을 처리하거나 그리는 작업만 합니다. 예를 들어 목록에 1,000개 요소가 있지만 10개만 표시되는 경우RecyclerView
는 화면에 10개 항목을 그리기만 합니다. 사용자가 스크롤하면RecyclerView
는 화면에 어떤 새 항목이 있어야 하는지 파악하고 그 항목을 표시만 합니다. - 항목이 화면에서 스크롤되면 항목의 뷰가 재활용됩니다. 다시 말해서, 항목이 화면에 스크롤되는 새로운 콘텐츠로 채워집니다. 이
RecyclerView
동작은 처리 시간을 크게 단축하고 목록이 유연하게 스크롤하는 데 도움이 됩니다. - 항목이 변경되면 전체 목록을 다시 그리는 대신
RecyclerView
에서 한 항목을 업데이트할 수 있습니다. 복잡한 항목 목록을 표시할 때 효율성을 크게 높일 수 있습니다.
한 뷰가 아래의 순서대로 ABC
데이터로 채워진 것을 볼 수 있습니다. 이 뷰가 화면에서 스크롤된 후에는 RecyclerView
는 이 뷰를 새 데이터인 XYZ
에 재사용합니다.
어댑터 패턴
다른 전기 콘센트를 사용하는 나라를 방문할 경우 어댑터를 사용하여 기기를 콘센트에 연결하는 방법을 알고 있을 것입니다. 어댑터를 사용하면 한 유형의 플러그를 다른 플러그로 변환할 수 있으며, 한 인터페이스를 다른 인터페이스로 변환합니다.
소프트웨어 엔지니어링의 어댑터 패턴은 객체가 다른 API와 함께 작동하도록 도와줍니다. RecyclerView
는 어댑터를 사용하여 데이터 저장 및 처리 방식을 변경하지 않고 앱 데이터를 RecyclerView
에서 표시할 수 있는 형식으로 변환합니다. 수면 추적 앱의 경우 ViewModel
를 변경하지 않고도 Room
데이터베이스의 데이터를 RecyclerView
이 표시하는 방법을 알 수 있도록 어댑터가 조정됩니다.
RecyclerView 구현
RecyclerView
에 데이터를 표시하려면 다음 부분이 필요합니다.
- 표시할 데이터입니다.
- 레이아웃 파일의 정의된 뷰로 사용하기 위해 레이아웃 파일에 정의된
RecyclerView
인스턴스. - 한 항목의 데이터 레이아웃.
모든 목록 항목이 동일하게 보이는 경우 모든 항목에 동일한 레이아웃을 사용할 수 있지만 필수는 아닙니다. 한 번에 하나의 항목 뷰를 만들어 데이터로 채울 수 있도록 항목 레이아웃을 프래그먼트 레이아웃과 별도로 만들어야 합니다. - 레이아웃 관리자.
레이아웃 관리자는 뷰에서 UI 구성요소의 조직 (레이아웃)을 처리합니다. - 뷰 홀더.
뷰 홀더는ViewHolder
클래스를 확장합니다. 항목 레이아웃에서 항목 하나를 표시하는 뷰 정보가 포함되어 있습니다. 뷰 홀더는RecyclerView
가 화면에서 뷰를 효율적으로 이동하기 위해 사용하는 정보도 추가합니다. - 어댑터:
어댑터가 데이터를RecyclerView
에 연결합니다.ViewHolder
에 표시될 수 있도록 데이터를 조정합니다.RecyclerView
는 어댑터를 사용하여 화면에 데이터를 표시하는 방법을 파악합니다.
이 작업에서는 RecyclerView
를 레이아웃 파일에 추가하고 Adapter
를 설정하여 수면 데이터를 RecyclerView
에 노출합니다.
1단계: LayoutManager로 RecyclerView 추가
이 단계에서는 fragment_sleep_tracker.xml
파일의 ScrollView
를 RecyclerView
로 바꿉니다.
- GitHub에서 RecyclerViewFundamentals-Starter 앱을 다운로드합니다.
- 앱을 빌드하고 실행합니다. 데이터가 간단한 텍스트로 표시되는 방식을 확인합니다.
- Android 스튜디오의 Design 탭에서
fragment_sleep_tracker.xml
레이아웃 파일을 엽니다. - Component Tree 창에서
ScrollView
을 삭제합니다. 이 작업을 수행하면ScrollView
내의TextView
도 삭제됩니다. - Palette 창에서 왼쪽에 있는 구성요소 유형 목록을 스크롤하여 Containers를 찾은 다음 선택합니다.
RecyclerView
를 Palette 창에서 Component Tree 창으로 드래그합니다.RecyclerView
를ConstraintLayout
내부에 배치합니다.
- 종속 항목을 추가할지 묻는 대화상자가 열리면 OK를 클릭하여 Android 스튜디오에서 Gradle 파일에
recyclerview
종속 항목을 추가하도록 합니다. 몇 초 후에 앱이 동기화됩니다.
- 모듈
build.gradle
파일을 열고 끝까지 스크롤한 다음 아래 코드와 비슷한 새 종속 항목을 기록해 둡니다.
implementation 'androidx.recyclerview:recyclerview:1.0.0'
fragment_sleep_tracker.xml
(으)로 다시 전환합니다.- Text 탭에서 아래에 표시된
RecyclerView
코드를 찾습니다.
<androidx.recyclerview.widget.RecyclerView
android:layout_width="match_parent"
android:layout_height="match_parent" />
RecyclerView
id
sleep_list
제공.
android:id="@+id/sleep_list"
RecyclerView
를 배치하여ConstraintLayout
내에서 화면의 나머지 부분을 차지합니다. 이렇게 하려면RecyclerView
의 상단을 Start 버튼으로, 하단을 Clear 버튼으로, 각 측면을 상위 요소로 제한합니다. Layout Editor 또는 XML에서 다음 코드를 사용하여 레이아웃 너비와 높이를 0dp로 설정합니다.
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toTopOf="@+id/clear_button"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/stop_button"
- 레이아웃 관리자를
RecyclerView
XML에 추가합니다. 모든RecyclerView
에는 목록에서 항목을 배치하는 방법을 알려주는 레이아웃 관리자가 필요합니다. Android는 기본적으로 전체 너비 행의 세로 목록에 항목을 배치하는LinearLayoutManager
를 제공합니다.
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
- Design 탭으로 전환하면 추가된 제약 조건으로 인해
RecyclerView
가 사용 가능한 공간을 채우도록 확장됩니다.
2단계: 목록 항목 레이아웃 및 텍스트 뷰 홀더 만들기
RecyclerView
는 컨테이너일 뿐입니다. 이 단계에서는 RecyclerView
의 내부 데이터 항목을 표시할 레이아웃과 인프라를 만듭니다.
가능한 한 빨리 RecyclerView
에 도달하려면 먼저 수면의 질만 숫자로 표시하는 간단한 목록 항목을 사용합니다. 이렇게 하려면 뷰 홀더(TextItemViewHolder
)가 필요합니다. 또한 데이터 뷰 TextView
도 필요합니다. 이후 단계에서 뷰 홀더와 모든 수면 데이터 배치 방법에 관해 자세히 알아봅니다.
text_item_view.xml
이라는 레이아웃 파일을 만듭니다. 루트 코드로 사용하는 것은 중요하지 않습니다. 템플릿 코드를 대체하기 때문입니다.text_item_view.xml
에서 지정된 코드를 모두 삭제합니다.- 시작과 끝에
16dp
패딩이 있고 텍스트 크기가24sp
인TextView
를 추가합니다. 너비가 상위 요소와 일치하도록 하고 높이가 콘텐츠를 래핑합니다. 이 뷰는RecyclerView
내부에 표시되므로ViewGroup
내부에 뷰를 배치할 필요가 없습니다.
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:textSize="24sp"
android:paddingStart="16dp"
android:paddingEnd="16dp"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
Util.kt
를 엽니다. 끝까지 스크롤하여 아래에 표시된 정의를 추가하여TextItemViewHolder
클래스를 만듭니다. 파일 하단의 마지막 닫는 중괄호 뒤에 코드를 삽입합니다. 이 뷰 홀더는 일시적이며 나중에 교체하므로 코드가Util.kt
에 들어갑니다.
class TextItemViewHolder(val textView: TextView): RecyclerView.ViewHolder(textView)
- 메시지가 표시되면
android.widget.TextView
와androidx.recyclerview.widget.RecyclerView
을 가져옵니다.
3단계: SleepNightAdapter 만들기
RecyclerView
구현의 핵심 작업은 어댑터를 만드는 것입니다. 항목 뷰의 간단한 뷰 홀더와 각 항목의 레이아웃이 있습니다. 이제 어댑터를 만들 수 있습니다. 어댑터가 뷰 홀더를 만들고 RecyclerView
를 표시할 데이터로 채웁니다.
sleeptracker
패키지에서SleepNightAdapter
라는 새 Kotlin 클래스를 만듭니다.SleepNightAdapter
클래스가RecyclerView.Adapter
를 확장하도록 합니다. 이 클래스는SleepNight
에서RecyclerView
가 사용할 수 있는 항목에 맞게 조정되므로SleepNightAdapter
라고 합니다. 어댑터에서 사용할 뷰 홀더를 알아야 하므로TextItemViewHolder
을 전달합니다. 메시지가 표시되면 필요한 구성요소를 가져오면 구현해야 하는 필수 메서드가 있으므로 오류가 표시됩니다.
class SleepNightAdapter: RecyclerView.Adapter<TextItemViewHolder>() {}
SleepNightAdapter
의 최상위 수준에서 데이터를 저장할listOf
SleepNight
변수를 만듭니다.
var data = listOf<SleepNight>()
SleepNightAdapter
에서getItemCount()
를 재정의하여data
의 수면 밤 목록 크기를 반환합니다.RecyclerView
는 어댑터에 표시할 항목 수를 알아야 하며,getItemCount()
를 호출하여 표시할 수 있습니다.
override fun getItemCount() = data.size
SleepNightAdapter
에서 아래와 같이onBindViewHolder()
함수를 재정의합니다.onBindViewHolder()
함수는RecyclerView
로 호출되어 지정된 위치에 하나의 목록 항목에 대한 데이터를 표시합니다. 따라서onBindViewHolder()
메서드는 두 가지 인수, 즉 뷰 홀더와 결합할 데이터의 위치를 사용합니다. 이 앱의 경우, 소유자는TextItemViewHolder
이며 위치는 게재순위입니다.
override fun onBindViewHolder(holder: TextItemViewHolder, position: Int) {
}
onBindViewHolder()
내에서 데이터의 지정된 위치에 항목 하나의 변수를 만듭니다.
val item = data[position]
- 생성한
ViewHolder
에textView
라는 속성이 있습니다.onBindViewHolder()
내부에서textView
의text
를 수면의 질 번호로 설정합니다. 이 코드는 숫자 목록만 표시하지만, 이 간단한 예를 통해 어댑터가 데이터를 뷰 홀더와 화면으로 가져오는 방법을 확인할 수 있습니다.
holder.textView.text = item.sleepQuality.toString()
SleepNightAdapter
에서RecyclerView
가 항목을 나타내는 뷰 홀더가 필요할 때 호출되는onCreateViewHolder()
을 재정의하고 구현합니다.
이 함수는 두 매개변수를 사용하며ViewHolder
를 반환합니다. 뷰 홀더를 포함하는 뷰 그룹인parent
매개변수는 항상RecyclerView
입니다.viewType
매개변수는 같은RecyclerView
에 뷰가 여러 개 있는 경우에 사용됩니다. 예를 들어 텍스트 뷰, 이미지, 동영상 목록을 모두 동일한RecyclerView
에 배치하면onCreateViewHolder()
함수는 사용할 뷰 유형을 알아야 합니다.
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TextItemViewHolder {
}
onCreateViewHolder()
에서LayoutInflater
의 인스턴스를 만듭니다.
레이아웃 인플레이터는 XML 레이아웃에서 뷰를 만드는 방법을 알고 있습니다.context
에는 뷰를 올바르게 확장하는 방법에 관한 정보가 있습니다. recycler 뷰용 어댑터에서는 항상parent
뷰 그룹(RecyclerView
)의 컨텍스트를 전달합니다.
val layoutInflater = LayoutInflater.from(parent.context)
onCreateViewHolder()
에서layoutinflater
에 확장해 달라고 요청하여view
를 만듭니다.
뷰의 XML 레이아웃과 뷰의parent
뷰 그룹을 전달합니다. 세 번째 부울인 부울은attachToRoot
입니다. 이 인수는false
여야 합니다. 왜냐하면 적절한 때가 되면RecyclerView
가 이 항목을 뷰 계층 구조에 추가하기 때문입니다.
val view = layoutInflater
.inflate(R.layout.text_item_view, parent, false) as TextView
onCreateViewHolder()
에서view
로 만든TextItemViewHolder
를 반환합니다.
return TextItemViewHolder(view)
- 어댑터가
data
에 변경사항이 있을 때RecyclerView
에 알려야 합니다.RecyclerView
가 데이터에 관해 알지 못하기 때문입니다. 어댑터에서 제공하는 뷰 홀더에 관해서만 알 수 있습니다.
표시되는 데이터가 변경된 경우RecyclerView
에 알리려면SleepNightAdapter
클래스 상단에 있는data
변수에 맞춤 setter를 추가합니다. setter에서data
에 새 값을 지정한 다음notifyDataSetChanged()
를 호출하여 새 데이터로 목록 다시 그리기를 트리거합니다.
var data = listOf<SleepNight>()
set(value) {
field = value
notifyDataSetChanged()
}
4단계: RecyclerView에 어댑터 알리기
RecyclerView
는 뷰 홀더를 가져오기 위해 사용할 어댑터에 관해 알아야 합니다.
SleepTrackerFragment.kt
를 엽니다.onCreateview()
에서 어댑터를 만듭니다.ViewModel
모델 생성 후 및return
문 앞에 이 코드를 삽입합니다.
val adapter = SleepNightAdapter()
adapter
를RecyclerView
와 연결합니다.
binding.sleepList.adapter = adapter
- 프로젝트를 정리하고 다시 빌드하여
binding
객체를 업데이트합니다.binding.sleepList
또는binding.FragmentSleepTrackerBinding
와 관련된 오류가 계속 표시되면 캐시를 무효화하고 다시 시작하세요. (파일 > 캐시 무효화 / 다시 시작 선택)
앱을 지금 실행하면 오류가 발생하지 않지만 시작을 탭한 다음 중지를 탭했을 때 표시되는 데이터가 표시되지 않습니다.
5단계: 어댑터에 데이터 가져오기
지금까지 어댑터와 어댑터의 데이터를 RecyclerView
로 가져오는 방법을 알아보았습니다. 이제 ViewModel
에서 어댑터로 데이터를 가져와야 합니다.
SleepTrackerViewModel
를 엽니다.- 모든 수면 밤을 저장하는
nights
변수를 찾습니다. 이는 표시할 데이터입니다.nights
변수는 데이터베이스에서getAllNights()
을 호출하여 설정합니다. - 이 변수에 액세스해야 하는 관찰자를 만들므로
nights
에서private
를 삭제합니다. 선언은 다음과 같이 표시됩니다.
val nights = database.getAllNights()
database
패키지에서SleepDatabaseDao
를 엽니다.getAllNights()
함수를 찾습니다. 이 함수는SleepNight
값 목록을LiveData
으로 반환합니다. 즉,nights
변수에는Room
에 의해 업데이트되는LiveData
가 포함되어 있으며nights
를 관찰하여 변경할 수 있습니다.SleepTrackerFragment
를 엽니다.onCreateView()
의adapter
생성 아래에서nights
변수에 관찰자를 만듭니다.
프래그먼트의viewLifecycleOwner
를 수명 주기 소유자로 제공하면RecyclerView
가 화면에 있는 경우에만 이 관찰자가 활성 상태가 됩니다.
sleepTrackerViewModel.nights.observe(viewLifecycleOwner, Observer {
})
- 관찰자 내에서
nights
에 null이 아닌 값을 가져올 때마다 값을 어댑터의data
에 할당합니다. 다음은 완성된 관찰자 코드 및 데이터 설정입니다.
sleepTrackerViewModel.nights.observe(viewLifecycleOwner, Observer {
it?.let {
adapter.data = it
}
})
- 코드를 빌드하고 실행합니다.
어댑터가 작동하는 경우 수면의 질이 목록으로 표시됩니다. 시작을 탭하면 왼쪽 스크린샷에 -1이 표시됩니다. 오른쪽 스크린샷은 중지를 탭하고 품질 평점을 선택한 후 업데이트된 수면의 질 번호를 보여줍니다.
6단계: 뷰 홀더의 재활용 방법 살펴보기
RecyclerView
뷰 홀더재활용 즉, 재사용됩니다. 뷰가 스크롤되어 화면에서 벗어나면 RecyclerView
는 뷰를 스크롤하려고 하는 뷰를 재사용합니다.
이러한 뷰 홀더는 재활용되므로 onBindViewHolder()
가 이전 항목이 뷰 홀더에서 설정했을 수 있는 맞춤설정을 설정하거나 재설정해야 합니다.
예를 들어 1 이하의 품질 평점을 받고 수면의 질이 낮은 뷰 홀더에서 텍스트 색상을 빨간색으로 설정할 수 있습니다.
SleepNightAdapter
클래스의onBindViewHolder()
끝에 다음 코드를 추가합니다.
if (item.sleepQuality <= 1) {
holder.textView.setTextColor(Color.RED) // red
}
- 앱을 실행합니다.
- 수면의 질이 낮은 데이터를 추가하면 숫자가 빨간색이 됩니다.
- 화면에 빨간색 높은 수가 표시될 때까지 수면의 질에 관한 높은 평점을 추가합니다.
RecyclerView
는 뷰 홀더를 재사용하므로 결국 높은 품질의 평점을 얻기 위해 빨간색 뷰 홀더 중 하나를 다시 사용합니다. 높은 등급은 빨간색으로 잘못 표시됩니다.
- 이 문제를 해결하려면
else
문을 추가하여 화질이 1보다 작거나 같지 않을 때 색상을 검은색으로 설정하세요.
두 조건이 모두 명시적이면 뷰 홀더는 각 항목에 올바른 텍스트 색상을 사용합니다.
if (item.sleepQuality <= 1) {
holder.textView.setTextColor(Color.RED) // red
} else {
// reset
holder.textView.setTextColor(Color.BLACK) // black
}
- 앱을 실행하면 숫자의 색상이 항상 정확해야 합니다.
수고하셨습니다. 이제 완전히 작동하는 기본 RecyclerView
를 확보했습니다.
이 작업에서는 간단한 뷰 홀더를 수면 시간 동안 더 많은 데이터를 표시할 수 있는 뷰 홀더로 바꿉니다.
Util.kt
에 추가한 간단한 ViewHolder
는 TextItemViewHolder
의 TextView
를 래핑합니다.
class TextItemViewHolder(val textView: TextView): RecyclerView.ViewHolder(textView)
그렇다면 RecyclerView
는 단순히 TextView
를 직접 사용하지 않는 이유는 무엇인가요? 이 한 줄의 코드는 다양한 기능을 제공합니다. ViewHolder
는 RecyclerView
내의 장소와 관련된 항목 뷰와 메타데이터를 설명합니다. RecyclerView
는 이 기능을 사용하여 목록이 스크롤될 때 뷰를 올바르게 배치하고 Adapter
에서 항목을 추가하거나 삭제할 때 애니메이션 애니메이션과 같은 흥미로운 작업을 실행합니다.
RecyclerView
가 ViewHolder
에 저장된 뷰에 액세스해야 하는 경우 뷰 홀더의 itemView
속성을 사용하여 액세스할 수 있습니다. RecyclerView
는 항목을 화면에 표시하고, 테두리와 같은 뷰 주위에 장식을 그리거나, 접근성을 구현할 때 itemView
를 사용합니다.
1단계: 항목 레이아웃 만들기
이 단계에서는 하나의 항목의 레이아웃 파일을 만듭니다. 레이아웃은 수면 품질을 위한 ImageView
와 수면 길이를 위한 TextView
, 텍스트 품질을 위한 TextView
가 있는 ConstraintLayout
로 구성됩니다. 이전에 레이아웃을 수행했으므로 제공된 XML 코드를 복사하여 붙여 넣습니다.
- 새 레이아웃 리소스 파일을 만들고 이름을
list_item_sleep_night
로 지정합니다. - 파일의 모든 코드를 아래 코드로 바꿉니다. 그런 다음 방금 만든 레이아웃을 숙지하세요.
<?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="wrap_content">
<ImageView
android:id="@+id/quality_image"
android:layout_width="@dimen/icon_size"
android:layout_height="60dp"
android:layout_marginStart="16dp"
android:layout_marginTop="8dp"
android:layout_marginBottom="8dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:srcCompat="@drawable/ic_sleep_5" />
<TextView
android:id="@+id/sleep_length"
android:layout_width="0dp"
android:layout_height="20dp"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="16dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/quality_image"
app:layout_constraintTop_toTopOf="@+id/quality_image"
tools:text="Wednesday" />
<TextView
android:id="@+id/quality_string"
android:layout_width="0dp"
android:layout_height="20dp"
android:layout_marginTop="8dp"
app:layout_constraintEnd_toEndOf="@+id/sleep_length"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="@+id/sleep_length"
app:layout_constraintTop_toBottomOf="@+id/sleep_length"
tools:text="Excellent!!!" />
</androidx.constraintlayout.widget.ConstraintLayout>
- Android 스튜디오에서 Design 탭으로 전환합니다. 디자인 보기에서 레이아웃은 아래의 왼쪽 스크린샷과 같이 표시됩니다. 청사진 뷰에서는 오른쪽 스크린샷과 같습니다.
2단계: ViewHolder 만들기
SleepNightAdapter.kt
를 엽니다.SleepNightAdapter
내에서ViewHolder
이라는 클래스를 만들고RecyclerView.ViewHolder
를 확장합니다.
class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView){}
ViewHolder
내에서 뷰에 관한 참조를 가져옵니다. 이ViewHolder
에서 업데이트할 뷰에 대한 참조가 필요합니다. 이ViewHolder
를 바인딩할 때마다 이미지와 두 텍스트 뷰 모두에 액세스해야 합니다. (나중에 데이터 결합을 사용하도록 이 코드를 변환합니다.)
val sleepLength: TextView = itemView.findViewById(R.id.sleep_length)
val quality: TextView = itemView.findViewById(R.id.quality_string)
val qualityImage: ImageView = itemView.findViewById(R.id.quality_image)
3단계: SleepNightAdapter에서 ViewHolder 사용
SleepNightAdapter
정의에서TextItemViewHolder
대신 방금 만든SleepNightAdapter.ViewHolder
를 사용합니다.
class SleepNightAdapter: RecyclerView.Adapter<SleepNightAdapter.ViewHolder>() {
onCreateViewHolder()
를 업데이트합니다.
onCreateViewHolder()
의 서명을 변경하여ViewHolder
를 반환합니다.- 올바른 레이아웃 리소스(
list_item_sleep_night
)를 사용하도록 레이아웃 팽창자를 변경합니다. - Cast를
TextView
로 삭제합니다. TextItemViewHolder
를 반환하는 대신ViewHolder
를 반환합니다.
다음은 완료된onCreateViewHolder()
함수입니다.
override fun onCreateViewHolder(
parent: ViewGroup, viewType: Int): ViewHolder {
val layoutInflater =
LayoutInflater.from(parent.context)
val view = layoutInflater
.inflate(R.layout.list_item_sleep_night,
parent, false)
return ViewHolder(view)
}
onBindViewHolder()
를 업데이트합니다.
holder
매개변수가TextItemViewHolder
대신ViewHolder
가 되도록onBindViewHolder()
의 서명을 변경합니다.onBindViewHolder()
내에서item
의 정의를 제외하고 모든 코드를 삭제합니다.- 이 뷰의
resources
참조를 보유하는val
res
를 정의합니다.
val res = holder.itemView.context.resources
sleepLength
텍스트 뷰의 텍스트를 재생 시간으로 설정합니다. 아래 코드를 복사하세요. 이 코드는 시작 코드와 함께 제공되는 형식 함수를 호출합니다.
holder.sleepLength.text = convertDurationToFormatted(item.startTimeMilli, item.endTimeMilli, res)
convertDurationToFormatted()
이 정의되어야 하므로 오류가 발생합니다.Util.kt
를 열고 코드의 관련 주석 및 관련 가져오기를 주석 처리합니다. (Code > Comment with Line Lines를 선택합니다.)onBindViewHolder()
로 돌아가서convertNumericQualityToString()
를 사용하여 품질을 설정합니다.
holder.quality.text= convertNumericQualityToString(item.sleepQuality, res)
- 이러한 함수를 직접 가져와야 할 수도 있습니다.
import com.example.android.trackmysleepquality.convertDurationToFormatted
import com.example.android.trackmysleepquality.convertNumericQualityToString
- 화질에 올바른 아이콘을 설정합니다. 시작 코드에서 새
ic_sleep_active
아이콘이 제공됩니다.
holder.qualityImage.setImageResource(when (item.sleepQuality) {
0 -> R.drawable.ic_sleep_0
1 -> R.drawable.ic_sleep_1
2 -> R.drawable.ic_sleep_2
3 -> R.drawable.ic_sleep_3
4 -> R.drawable.ic_sleep_4
5 -> R.drawable.ic_sleep_5
else -> R.drawable.ic_sleep_active
})
- 다음은 완료된
onBindViewHolder()
함수입니다. 이 함수는ViewHolder
의 모든 데이터를 설정합니다.
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val item = data[position]
val res = holder.itemView.context.resources
holder.sleepLength.text = convertDurationToFormatted(item.startTimeMilli, item.endTimeMilli, res)
holder.quality.text= convertNumericQualityToString(item.sleepQuality, res)
holder.qualityImage.setImageResource(when (item.sleepQuality) {
0 -> R.drawable.ic_sleep_0
1 -> R.drawable.ic_sleep_1
2 -> R.drawable.ic_sleep_2
3 -> R.drawable.ic_sleep_3
4 -> R.drawable.ic_sleep_4
5 -> R.drawable.ic_sleep_5
else -> R.drawable.ic_sleep_active
})
}
- 앱을 실행합니다. 디스플레이가 아래 스크린샷과 같이 수면 상태 아이콘과 수면 시간 텍스트 및 수면의 질을 보여 줍니다.
RecyclerView
완료 Adapter
및 ViewHolder
를 구현하는 방법을 알아봤습니다. 그리고 이를 조합하여 RecyclerView
Adapter
로 목록을 표시하는 방법을 알아보았습니다.
지금까지 코드는 어댑터와 뷰 홀더를 만드는 프로세스를 보여줍니다. 그러나 이 코드는 개선할 수 있습니다. 표시할 코드와 뷰 홀더를 관리하는 코드가 혼합되어 있고, onBindViewHolder()
가 ViewHolder
업데이트 방법에 관한 세부정보를 알고 있습니다.
프로덕션 앱에는 여러 뷰 홀더, 보다 복잡한 어댑터, 여러 개발자가 변경사항을 적용할 수 있습니다. 뷰 홀더와 관련된 모든 것이 뷰 홀더에만 있도록 코드를 구조화해야 합니다.
1단계: onBindViewHolder() 리팩터링
이 단계에서는 코드를 리팩터링하고 모든 뷰 홀더 기능을 ViewHolder
로 이동합니다. 리팩터링의 목적은 앱이 사용자에게 표시되는 방식을 변경하는 것이 아니라 개발자가 코드에서 더 쉽고 안전하게 작업할 수 있도록 하는 것입니다. 다행히 Android 스튜디오에는 유용한 도구가 있습니다.
SleepNightAdapter
의onBindViewHolder()
에서item
변수를 선언하는 문을 제외한 모든 항목을 선택합니다.- 마우스 오른쪽 버튼으로 클릭한 다음 Refactor > Extract > Function을 선택합니다.
- 함수 이름을
bind
로 지정하고 추천 매개변수를 수락합니다. 확인을 클릭합니다.bind()
함수는onBindViewHolder()
아래에 있습니다.
private fun bind(holder: ViewHolder, item: SleepNight) {
val res = holder.itemView.context.resources
holder.sleepLength.text = convertDurationToFormatted(item.startTimeMilli, item.endTimeMilli, res)
holder.quality.text = convertNumericQualityToString(item.sleepQuality, res)
holder.qualityImage.setImageResource(when (item.sleepQuality) {
0 -> R.drawable.ic_sleep_0
1 -> R.drawable.ic_sleep_1
2 -> R.drawable.ic_sleep_2
3 -> R.drawable.ic_sleep_3
4 -> R.drawable.ic_sleep_4
5 -> R.drawable.ic_sleep_5
else -> R.drawable.ic_sleep_active
})
}
bind()
holder
매개변수의 단어holder
에 커서를 놓습니다.Alt+Enter
(Mac은Option+Enter
)을 눌러 의도 메뉴를 엽니다. Convert parameters to receiver를 선택하여 다음 서명이 있는 확장 함수로 변환합니다.
private fun ViewHolder.bind(item: SleepNight) {...}
bind()
함수를 잘라ViewHolder
에 붙여넣습니다.bind()
을 공개로 설정합니다.- 필요한 경우
bind()
를 어댑터로 가져옵니다. - 이제
ViewHolder
에 있으므로 서명의ViewHolder
부분을 삭제할 수 있습니다. 다음은ViewHolder
클래스의bind()
함수에 관한 최종 코드입니다.
fun bind(item: SleepNight) {
val res = itemView.context.resources
sleepLength.text = convertDurationToFormatted(
item.startTimeMilli, item.endTimeMilli, res)
quality.text = convertNumericQualityToString(
item.sleepQuality, res)
qualityImage.setImageResource(when (item.sleepQuality) {
0 -> R.drawable.ic_sleep_0
1 -> R.drawable.ic_sleep_1
2 -> R.drawable.ic_sleep_2
3 -> R.drawable.ic_sleep_3
4 -> R.drawable.ic_sleep_4
5 -> R.drawable.ic_sleep_5
else -> R.drawable.ic_sleep_active
})
}
2단계: onCreateViewHolder 리팩터링
어댑터의 onCreateViewHolder()
메서드는 현재 ViewHolder
의 레이아웃 리소스에서 뷰를 확장합니다. 그러나 인플레이션은 어댑터와 아무 관련이 없으며 ViewHolder
와 관련된 모든 작업을 실행합니다. 확장은 ViewHolder
에서 발생해야 합니다.
onCreateViewHolder()
에서 함수 본문에 모든 코드를 선택합니다.- 마우스 오른쪽 버튼으로 클릭한 다음 Refactor > Extract > Function을 선택합니다.
- 함수 이름을
from
로 지정하고 추천 매개변수를 수락합니다. 확인을 클릭합니다. - 함수 이름
from
에 커서를 놓습니다.Alt+Enter
(Mac은Option+Enter
)을 눌러 의도 메뉴를 엽니다. - 컴패니언 객체로 이동을 선택합니다.
from()
함수는ViewHolder
객체에서 호출되지 않고ViewHolder
클래스에서 호출되도록 컴패니언 객체에 있어야 합니다. companion
객체를ViewHolder
클래스로 이동합니다.from()
을 공개로 설정합니다.onCreateViewHolder()
에서return
문을 변경하여ViewHolder
클래스의from()
호출 결과를 반환합니다.
완료된onCreateViewHolder()
및from()
메서드는 아래 코드와 같이 표시되며 오류 없이 빌드되고 실행됩니다.
override fun onCreateViewHolder(parent: ViewGroup, viewType:
Int): ViewHolder {
return ViewHolder.from(parent)
}
companion object {
fun from(parent: ViewGroup): ViewHolder {
val layoutInflater = LayoutInflater.from(parent.context)
val view = layoutInflater
.inflate(R.layout.list_item_sleep_night, parent, false)
return ViewHolder(view)
}
}
- 생성자가 비공개가 되도록
ViewHolder
클래스의 서명을 변경합니다. 이제from()
가 새ViewHolder
인스턴스를 반환하는 메서드이므로 다른 사람이 더 이상ViewHolder
의 생성자를 호출할 이유가 없습니다.
class ViewHolder private constructor(itemView: View) : RecyclerView.ViewHolder(itemView){
- 앱을 실행합니다. 앱은 이전과 동일하게 빌드하고 리팩터링한 후 원하는 결과를 실행해야 합니다.
Android 스튜디오 프로젝트: RecyclerViewFundamentals
- 데이터 목록 또는 그리드 표시는 Android에서 가장 일반적인 UI 작업 중 하나입니다.
RecyclerView
는 매우 큰 목록을 표시할 때도 효율적으로 작동하도록 설계되었습니다. RecyclerView
는 현재 화면에 표시된 항목을 처리하거나 그리는 데 필요한 작업만 처리합니다.- 항목이 스크롤되어 화면에서 벗어나면 뷰가 재활용됩니다. 다시 말해서, 항목이 화면에 스크롤되는 새로운 콘텐츠로 채워집니다.
- 소프트웨어 엔지니어링의 어댑터 패턴은 객체가 다른 API와 함께 작동하는 데 도움이 됩니다.
RecyclerView
은 어댑터를 사용하여 데이터를 저장하고 처리하는 방식을 변경할 필요 없이 앱 데이터를 표시할 수 있는 형식으로 변환합니다.
RecyclerView
에 데이터를 표시하려면 다음 부분이 필요합니다.
- RecyclerView
RecyclerView
의 인스턴스를 만들려면 레이아웃 파일에서<RecyclerView>
요소를 정의합니다. - LayoutManager
RecyclerView
는LayoutManager
를 사용하여RecyclerView
의 항목 레이아웃을 그리드 또는 선형 목록에 배치합니다.
레이아웃 파일의<RecyclerView>
에서app:layoutManager
속성을 레이아웃 관리자 (예:LinearLayoutManager
또는GridLayoutManager
)로 설정합니다.
또한RecyclerView
에 대해LayoutManager
를 프로그래매틱 방식으로 설정할 수도 있습니다. (이 방법은 이후 Codelab에서 다룹니다.) - 각 항목의 레이아웃
XML 레이아웃 파일에서 하나의 데이터 항목의 레이아웃을 만듭니다. - 어댑터
데이터를 준비하는 어댑터 및ViewHolder
에 데이터가 표시되는 방식을 만듭니다. 어댑터를RecyclerView
와 연결합니다.RecyclerView
을 실행할 때 어댑터를 사용하여 화면에 데이터를 표시하는 방법을 파악합니다.
어댑터는 다음 메서드를 구현하여 항목 수를 반환해야 합니다.
–onCreateViewHolder()
는 목록의 항목에 대한ViewHolder
을 반환합니다.
–onBindViewHolder()
는 목록의 항목에 관한 데이터를 조정합니다. - ViewHolder
ViewHolder
는 항목의 레이아웃에서 항목 하나를 표시하기 위한 뷰 정보를 포함합니다. - 어댑터의
onBindViewHolder()
메서드는 데이터를 뷰에 맞게 조정합니다. 이 메서드는 항상 재정의됩니다. 일반적으로onBindViewHolder()
는 항목의 레이아웃을 확장하고 레이아웃의 뷰에 데이터를 배치합니다. RecyclerView
은 데이터를 알지 못하므로 데이터가 변경되면Adapter
는RecyclerView
에 알려야 합니다.notifyDataSetChanged()
를 사용하여Adapter
에 데이터가 변경되었음을 알립니다.
Udacity 과정:
Android 개발자 문서:
이 섹션에는 강사가 진행하는 과정의 일부로 이 Codelab을 통해 작업하는 학생들의 숙제 과제가 나와 있습니다. 강사는 다음을 처리합니다.
- 필요한 경우 과제를 할당합니다.
- 학생에게 과제 과제를 제출하는 방법을 알려주세요.
- 과제 과제를 채점합니다.
강사는 이러한 추천을 원하는 만큼 사용할 수 있으며 다른 적절한 숙제를 할당해도 좋습니다.
이 Codelab을 직접 학습하고 있다면 언제든지 숙제를 통해 지식을 확인해 보세요.
답변
질문 1
RecyclerView
는 항목을 어떻게 표시하나요? 해당하는 보기를 모두 선택하세요.
▢ 항목을 목록 또는 그리드로 표시합니다.
▢ 세로 또는 가로로 스크롤합니다.
▢ 태블릿과 같은 더 큰 기기에서는 대각선으로 스크롤합니다.
▢ 목록 또는 그리드가 사용 사례에 충분하지 않으면 맞춤 레이아웃을 허용합니다.
질문 2
RecyclerView
을 사용하면 어떤 이점이 있나요? 해당하는 보기를 모두 선택하세요.
▢ 대형 목록을 효율적으로 표시
▢ 데이터를 자동으로 업데이트합니다.
▢: 항목이 업데이트, 삭제 또는 목록에 추가될 때 새로고침할 필요성을 최소화합니다.
▢ 화면에서 스크롤되는 다음 항목을 표시하기 위해 화면 밖으로 스크롤되는 뷰를 재사용합니다.
질문 3
어댑터를 사용하는 이유는 무엇인가요? 해당하는 보기를 모두 선택하세요.
▢ 우려사항을 분리하면 코드를 더 쉽게 변경하고 테스트할 수 있습니다.
▢ RecyclerView
는 표시되는 데이터에 구속받지 않습니다.
▢ 데이터 처리 레이어는 데이터 표시 방식에 신경쓰지 않아도 됩니다.
▢ 앱은 더 빠르게 실행됩니다.
질문 4
ViewHolder
에 관한 다음 설명 중 참인 것은 무엇인가요? 해당하는 보기를 모두 선택하세요.
▢ ViewHolder
레이아웃은 XML 레이아웃 파일에서 정의됩니다.
▢ 데이터 세트의 데이터 단위마다 ViewHolder
가 1개씩 있습니다.
▢ RecyclerView
에 ViewHolder
가 두 개 이상 있어야 합니다.
▢ Adapter
는 데이터를 ViewHolder
에 바인딩합니다.
다음 강의 시작: