Codelab ini adalah bagian dari kursus Dasar-Dasar Android Kotlin. Anda akan mendapatkan manfaat maksimal dari kursus ini jika menyelesaikan codelab secara berurutan. Semua codelab kursus tercantum di halaman landing codelab Dasar-Dasar Android Kotlin.
Pengantar
Dalam codelab ini, Anda akan mempelajari cara menambahkan header yang mencakup lebar daftar yang ditampilkan di RecyclerView. Anda akan membangun aplikasi pelacak tidur dari codelab sebelumnya.
Yang harus sudah Anda ketahui
- Cara membuat antarmuka pengguna dasar menggunakan aktivitas, fragmen, dan tampilan.
- Cara beralih antar-fragmen, dan cara menggunakan
safeArgsuntuk meneruskan data antar-fragmen. - Melihat model, melihat factory model, transformasi, dan
LiveDataserta pengamatnya. - Cara membuat database
Room, membuat DAO, dan menentukan entity. - Cara menggunakan coroutine untuk interaksi database dan tugas berjalan lama lainnya.
- Cara menerapkan
RecyclerViewdasar denganAdapter,ViewHolder, dan tata letak item. - Cara menerapkan data binding untuk
RecyclerView. - Cara membuat dan menggunakan adaptor binding untuk mengubah data.
- Cara menggunakan
GridLayoutManager. - Cara merekam dan menangani klik pada item di
RecyclerView.
Yang akan Anda pelajari
- Cara menggunakan lebih dari satu
ViewHolderdenganRecyclerViewuntuk menambahkan item dengan tata letak yang berbeda. Khususnya, cara menggunakanViewHolderkedua untuk menambahkan header di atas item yang ditampilkan diRecyclerView.
Yang akan Anda lakukan
- Bangun aplikasi TrackMySleepQuality dari codelab sebelumnya dalam seri ini.
- Tambahkan header yang mencakup lebar layar di atas malam tidur yang ditampilkan di
RecyclerView.
Aplikasi pelacak tidur yang Anda mulai memiliki tiga layar, yang diwakili oleh fragmen, seperti yang ditunjukkan pada gambar di bawah.
|
|
|
Layar pertama, yang ditampilkan di sebelah kiri, memiliki tombol untuk memulai dan menghentikan pelacakan. Layar menampilkan beberapa data tidur pengguna. Tombol Hapus akan menghapus semua data yang telah dikumpulkan aplikasi untuk pengguna secara permanen. Layar kedua, yang ditampilkan di tengah, adalah untuk memilih rating kualitas tidur. Layar ketiga adalah tampilan detail yang terbuka saat pengguna mengetuk item dalam petak.
Aplikasi ini menggunakan arsitektur yang disederhanakan dengan pengontrol UI, model tampilan dan LiveData, serta database Room untuk mempertahankan data tidur.

Dalam codelab ini, Anda akan menambahkan header ke petak item yang ditampilkan. Layar utama akhir Anda akan terlihat seperti ini:

Codelab ini mengajarkan prinsip umum menyertakan item yang menggunakan tata letak berbeda dalam RecyclerView. Salah satu contoh umumnya adalah memiliki header dalam daftar atau petak Anda. Daftar dapat memiliki satu header untuk mendeskripsikan konten item. Daftar juga dapat memiliki beberapa header untuk mengelompokkan dan memisahkan item dalam satu daftar.
RecyclerView tidak mengetahui apa pun tentang data Anda atau jenis tata letak setiap item. LayoutManager mengatur item di layar, tetapi adaptor menyesuaikan data yang akan ditampilkan dan meneruskan penampung tampilan ke RecyclerView. Jadi, Anda akan menambahkan kode untuk membuat header di adaptor.
Dua cara menambahkan header
Di RecyclerView, setiap item dalam daftar sesuai dengan nomor indeks yang dimulai dari 0. Contoh:
[Data Aktual] -> [Tampilan Adaptor]
[0: SleepNight] -> [0: SleepNight]
[1: SleepNight] -> [1: SleepNight]
[2: SleepNight] -> [2: SleepNight]
Salah satu cara untuk menambahkan header ke daftar adalah dengan mengubah adaptor untuk menggunakan ViewHolder yang berbeda dengan memeriksa indeks tempat header perlu ditampilkan. Adapter akan bertanggung jawab untuk melacak header. Misalnya, untuk menampilkan header di bagian atas tabel, Anda harus menampilkan ViewHolder yang berbeda untuk header saat menyusun item berindeks nol. Kemudian, semua item lainnya akan dipetakan dengan offset header, seperti yang ditunjukkan di bawah.
[Data Aktual] -> [Tampilan Adaptor]
[0: Header]
[0: SleepNight] -> [1: SleepNight]
[1: SleepNight] -> [2: SleepNight]
[2: SleepNight] -> [3: SleepNight.
Cara lain untuk menambahkan header adalah dengan mengubah set data pendukung untuk petak data Anda. Karena semua data yang perlu ditampilkan disimpan dalam daftar, Anda dapat mengubah daftar untuk menyertakan item yang mewakili header. Hal ini sedikit lebih mudah dipahami, tetapi mengharuskan Anda memikirkan cara mendesain objek, sehingga Anda dapat menggabungkan berbagai jenis item ke dalam satu daftar. Dengan cara ini, adaptor akan menampilkan item yang diteruskan kepadanya. Jadi, item di posisi 0 adalah header, dan item di posisi 1 adalah SleepNight, yang dipetakan langsung ke apa yang ada di layar.
[Data Aktual] -> [Tampilan Adaptor]
[0: Header] -> [0: Header]
[1: SleepNight] -> [1: SleepNight]
[2: SleepNight] -> [2: SleepNight]
[3: SleepNight] -> [3: SleepNight]
Setiap metodologi memiliki kelebihan dan kekurangan. Mengubah set data tidak banyak mengubah kode adaptor lainnya, dan Anda dapat menambahkan logika header dengan memanipulasi daftar data. Di sisi lain, penggunaan ViewHolder yang berbeda dengan memeriksa indeks untuk header memberikan lebih banyak kebebasan pada tata letak header. Hal ini juga memungkinkan adaptor menangani cara data diadaptasi ke tampilan tanpa mengubah data pendukung.
Dalam codelab ini, Anda akan memperbarui RecyclerView untuk menampilkan header di awal daftar. Dalam hal ini, aplikasi Anda akan menggunakan ViewHolder yang berbeda untuk header dan item data. Aplikasi akan memeriksa indeks daftar untuk menentukan ViewHolder mana yang akan digunakan.
Langkah 1: Buat class DataItem
Untuk mengabstraksi jenis item dan membiarkan adaptor hanya menangani "item", Anda dapat membuat class penampung data yang merepresentasikan SleepNight atau Header. Set data Anda kemudian akan menjadi daftar item penampung data.
Anda dapat mendapatkan aplikasi awal dari GitHub, atau melanjutkan penggunaan aplikasi SleepTracker yang Anda buat di codelab sebelumnya.
- Download kode RecyclerViewHeaders-Starter dari GitHub. Direktori RecyclerViewHeaders-Starter berisi versi awal aplikasi SleepTracker yang diperlukan untuk codelab ini. Anda juga dapat melanjutkan dengan aplikasi yang telah selesai dari codelab sebelumnya jika mau.
- Buka SleepNightAdapter.kt.
- Di bawah class
SleepNightListener, di tingkat teratas, tentukan classsealedyang disebutDataItemyang merepresentasikan item data.
Classsealedmenentukan jenis tertutup, yang berarti semua subclassDataItemharus ditentukan dalam file ini. Akibatnya, jumlah subkelas diketahui oleh compiler. Bagian lain dari kode Anda tidak dapat menentukan jenisDataItembaru yang dapat merusak adaptor Anda.
sealed class DataItem {
}- Di dalam isi class
DataItem, tentukan dua class yang merepresentasikan berbagai jenis item data. Yang pertama adalahSleepNightItem, yang merupakan wrapper di sekitarSleepNight, sehingga mengambil satu nilai yang disebutsleepNight. Untuk menjadikannya bagian dari class tertutup, buat class tersebut memperluasDataItem.
data class SleepNightItem(val sleepNight: SleepNight): DataItem()- Class kedua adalah
Header, untuk merepresentasikan header. Karena header tidak memiliki data aktual, Anda dapat mendeklarasikannya sebagaiobject. Artinya, hanya akan ada satu instanceHeader. Sekali lagi, buat class ini memperluasDataItem.
object Header: DataItem()- Di dalam
DataItem, di tingkat class, tentukan propertiabstractLongbernamaid. Saat adaptor menggunakanDiffUtiluntuk menentukan apakah dan bagaimana item telah berubah,DiffItemCallbackperlu mengetahui ID setiap item. Anda akan melihat error, karenaSleepNightItemdanHeaderperlu mengganti properti abstrakid.
abstract val id: Long- Di
SleepNightItem, gantiiduntuk menampilkannightId.
override val id = sleepNight.nightId- Di
Header, gantiiduntuk menampilkanLong.MIN_VALUE, yang merupakan angka yang sangat, sangat kecil (secara harfiah, -2 pangkat 63). Jadi, ID ini tidak akan pernah bertentangan dengannightIdyang ada.
override val id = Long.MIN_VALUE- Kode yang sudah selesai akan terlihat seperti ini, dan aplikasi Anda akan dibangun tanpa error.
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
}
}Langkah 2: Buat ViewHolder untuk Header
- Buat tata letak untuk header dalam file resource tata letak baru bernama header.xml yang menampilkan
TextView. Tidak ada yang menarik tentang hal ini, jadi berikut kodenya.
<?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" />- Ekstrak
"Sleep Results"ke dalam resource string dan panggilheader_text.
<string name="header_text">Sleep Results</string>- Di SleepNightAdapter.kt, di dalam
SleepNightAdapter, di atas classViewHolder, buat classTextViewHolderbaru. Class ini meng-inflate tata letak textview.xml, dan menampilkan instanceTextViewHolder. Karena Anda pernah melakukannya sebelumnya, berikut adalah kodenya, dan Anda harus mengimporViewdanR:
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)
}
}
}Langkah 3: Perbarui SleepNightAdapter
Selanjutnya, Anda perlu memperbarui deklarasi SleepNightAdapter. Daripada hanya mendukung satu jenis ViewHolder, class ini harus dapat menggunakan jenis penampung tampilan apa pun.
Menentukan jenis item
- Di
SleepNightAdapter.kt, di tingkat teratas, di bawah pernyataanimportdan di atasSleepNightAdapter, tentukan dua konstanta untuk jenis tampilan.RecyclerViewharus membedakan jenis tampilan setiap item, sehingga dapat menetapkan holder tampilan dengan benar.
private val ITEM_VIEW_TYPE_HEADER = 0
private val ITEM_VIEW_TYPE_ITEM = 1- Di dalam
SleepNightAdapter, buat fungsi untuk menggantigetItemViewType()guna menampilkan header atau konstanta item yang tepat, bergantung pada jenis item saat ini.
override fun getItemViewType(position: Int): Int {
return when (getItem(position)) {
is DataItem.Header -> ITEM_VIEW_TYPE_HEADER
is DataItem.SleepNightItem -> ITEM_VIEW_TYPE_ITEM
}
}Perbarui definisi SleepNightAdapter
- Dalam definisi
SleepNightAdapter, perbarui argumen pertama untukListAdapterdariSleepNightmenjadiDataItem. - Dalam definisi
SleepNightAdapter, ubah argumen generik kedua untukListAdapterdariSleepNightAdapter.ViewHoldermenjadiRecyclerView.ViewHolder. Anda akan melihat beberapa error untuk pembaruan yang diperlukan, dan header class Anda akan terlihat seperti yang ditunjukkan di bawah.
class SleepNightAdapter(val clickListener: SleepNightListener):
ListAdapter<DataItem, RecyclerView.ViewHolder>(SleepNightDiffCallback()) {Mengupdate onCreateViewHolder()
- Ubah tanda tangan
onCreateViewHolder()untuk menampilkanRecyclerView.ViewHolder.
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder- Perluas penerapan metode
onCreateViewHolder()untuk menguji dan menampilkan holder tampilan yang sesuai untuk setiap jenis item. Metode yang diperbarui akan terlihat seperti kode di bawah.
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}")
}
}Mengupdate onBindViewHolder()
- Ubah jenis parameter
onBindViewHolder()dariViewHoldermenjadiRecyclerView.ViewHolder.
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int)- Tambahkan kondisi untuk hanya menetapkan data ke penampung tampilan jika penampung adalah
ViewHolder.
when (holder) {
is ViewHolder -> {...}- Transmisikan jenis objek yang ditampilkan oleh
getItem()keDataItem.SleepNightItem. FungsionBindViewHolder()yang sudah selesai akan terlihat seperti ini.
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
when (holder) {
is ViewHolder -> {
val nightItem = getItem(position) as DataItem.SleepNightItem
holder.bind(nightItem.sleepNight, clickListener)
}
}
}Memperbarui callback diffUtil
- Ubah metode di
SleepNightDiffCallbackuntuk menggunakan classDataItembaru, bukanSleepNight. Tekan peringatan lint seperti yang ditunjukkan dalam kode di bawah.
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
}
}Menambahkan dan mengirimkan header
- Di dalam
SleepNightAdapter, di bawahonCreateViewHolder(), tentukan fungsiaddHeaderAndSubmitList()seperti yang ditunjukkan di bawah. Fungsi ini menggunakan daftarSleepNight. Daripada menggunakansubmitList(), yang disediakan olehListAdapter, untuk mengirimkan daftar, Anda akan menggunakan fungsi ini untuk menambahkan header, lalu mengirimkan daftar.
fun addHeaderAndSubmitList(list: List<SleepNight>?) {}- Di dalam
addHeaderAndSubmitList(), jika daftar yang diteruskan adalahnull, hanya tampilkan header. Jika tidak, lampirkan header ke bagian atas daftar, lalu kirimkan daftar.
val items = when (list) {
null -> listOf(DataItem.Header)
else -> listOf(DataItem.Header) + list.map { DataItem.SleepNightItem(it) }
}
submitList(items)- Buka SleepTrackerFragment.kt dan ubah panggilan ke
submitList()menjadiaddHeaderAndSubmitList().
- Jalankan aplikasi Anda dan amati cara header Anda ditampilkan sebagai item pertama dalam daftar item tidur.

Ada dua hal yang perlu diperbaiki untuk aplikasi ini. Satu terlihat, dan satu lagi tidak.
- Header muncul di sudut kiri atas, dan tidak mudah dibedakan.
- Hal ini tidak terlalu penting untuk daftar pendek dengan satu header, tetapi Anda tidak boleh melakukan manipulasi daftar di
addHeaderAndSubmitList()pada thread UI. Bayangkan daftar dengan ratusan item, beberapa header, dan logika untuk memutuskan tempat item perlu disisipkan. Tugas ini termasuk dalam coroutine.
Ubah addHeaderAndSubmitList() untuk menggunakan coroutine:
- Di level teratas dalam class
SleepNightAdapter, tentukanCoroutineScopedenganDispatchers.Default.
private val adapterScope = CoroutineScope(Dispatchers.Default)- Di
addHeaderAndSubmitList(), luncurkan coroutine diadapterScopeuntuk memanipulasi daftar. Kemudian, beralihlah ke konteksDispatchers.Mainuntuk mengirimkan daftar, seperti yang ditunjukkan dalam kode di bawah.
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)
}
}
}- Kode Anda akan dibangun dan dijalankan, dan Anda tidak akan melihat perbedaan apa pun.
Saat ini, header memiliki lebar yang sama dengan item lain di petak, yang menempati satu rentang secara horizontal dan vertikal. Seluruh petak memuat tiga item dengan lebar rentang satu secara horizontal, sehingga header harus menggunakan tiga rentang secara horizontal.
Untuk memperbaiki lebar header, Anda perlu memberi tahu GridLayoutManager kapan data harus mencakup semua kolom. Anda dapat melakukannya dengan mengonfigurasi SpanSizeLookup pada GridLayoutManager. Ini adalah objek konfigurasi yang digunakan GridLayoutManager untuk menentukan jumlah rentang yang akan digunakan untuk setiap item dalam daftar.
- Buka SleepTrackerFragment.kt.
- Temukan kode tempat Anda menentukan
manager, di bagian akhironCreateView().
val manager = GridLayoutManager(activity, 3)- Di bawah
manager, tentukanmanager.spanSizeLookup, seperti yang ditunjukkan. Anda perlu membuatobjectkarenasetSpanSizeLookuptidak menerima lambda. Untuk membuatobjectdi Kotlin, ketikobject : classname, dalam hal iniGridLayoutManager.SpanSizeLookup.
manager.spanSizeLookup = object : GridLayoutManager.SpanSizeLookup() {
}- Anda mungkin mendapatkan error compiler untuk memanggil konstruktor. Jika Anda melakukannya, buka menu niat dengan
Option+Enter(Mac) atauAlt+Enter(Windows) untuk menerapkan panggilan konstruktor.
- Kemudian, Anda akan mendapatkan error di
objectyang menyatakan bahwa Anda perlu mengganti metode. Letakkan kursor padaobject, tekanOption+Enter(Mac) atauAlt+Enter(Windows) untuk membuka menu maksud, lalu ganti metodegetSpanSize().
- Di isi
getSpanSize(), tampilkan ukuran rentang yang tepat untuk setiap posisi. Posisi 0 memiliki ukuran rentang 3, dan posisi lainnya memiliki ukuran rentang 1. Kode yang sudah selesai akan terlihat seperti kode di bawah:
manager.spanSizeLookup = object : GridLayoutManager.SpanSizeLookup() {
override fun getSpanSize(position: Int) = when (position) {
0 -> 3
else -> 1
}
}- Untuk meningkatkan tampilan header, buka header.xml dan tambahkan kode ini ke file tata letak header.xml.
android:textColor="@color/white_text_color"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="16dp"
android:background="@color/colorAccent"- Jalankan aplikasi Anda. Tampilannya akan terlihat seperti screenshot di bawah.

Selamat! Anda sudah selesai.
Project Android Studio: RecyclerViewHeaders
- Header umumnya adalah item yang mencakup lebar daftar dan berfungsi sebagai judul atau pemisah. Daftar dapat memiliki satu header untuk mendeskripsikan konten item, atau beberapa header untuk mengelompokkan item dan memisahkan item satu sama lain.
RecyclerViewdapat menggunakan beberapa penampung tampilan untuk mengakomodasi kumpulan item heterogen; misalnya, header dan item daftar.- Salah satu cara untuk menambahkan header adalah dengan mengubah adaptor Anda agar menggunakan
ViewHolderyang berbeda dengan memeriksa indeks tempat header Anda perlu ditampilkan.Adapterbertanggung jawab untuk melacak header. - Cara lain untuk menambahkan header adalah dengan mengubah set data pendukung (daftar) untuk petak data Anda, seperti yang Anda lakukan dalam codelab ini.
Berikut adalah langkah-langkah utama untuk menambahkan header:
- Abstrakkan data dalam daftar Anda dengan membuat
DataItemyang dapat menyimpan header atau data. - Buat penampung tampilan dengan tata letak untuk header di adaptor.
- Perbarui adaptor dan metodenya untuk menggunakan jenis
RecyclerView.ViewHolderapa pun. - Di
onCreateViewHolder(), tampilkan jenis penampung tampilan yang benar untuk item data. - Perbarui
SleepNightDiffCallbackagar berfungsi dengan classDataItem. - Buat fungsi
addHeaderAndSubmitList()yang menggunakan coroutine untuk menambahkan header ke dataset, lalu panggilsubmitList(). - Terapkan
GridLayoutManager.SpanSizeLookup()agar hanya header yang memiliki lebar tiga rentang.
Kursus Udacity:
Dokumentasi developer Android:
Bagian ini mencantumkan kemungkinan tugas pekerjaan rumah untuk siswa yang mengerjakan codelab ini sebagai bagian dari kursus yang dipimpin oleh instruktur. Instruktur menentukan hal berikut:
- Memberikan pekerjaan rumah jika diperlukan.
- Memberi tahu siswa cara mengirimkan tugas pekerjaan rumah.
- Memberi nilai tugas pekerjaan rumah.
Instruktur bisa menggunakan saran ini sesuai kebutuhan, dan bebas menugaskan pekerjaan rumah lain yang dirasa cocok.
Jika Anda menyelesaikan codelab ini sendiri, gunakan tugas pekerjaan rumah ini untuk menguji pengetahuan Anda.
Jawab pertanyaan-pertanyaan berikut
Pertanyaan 1
Manakah dari pernyataan berikut yang benar tentang ViewHolder?
▢ Adaptor dapat menggunakan beberapa class ViewHolder untuk menampung header dan berbagai jenis data.
▢ Anda dapat memiliki tepat satu holder tampilan untuk data, dan satu holder tampilan untuk header.
▢ RecyclerView mendukung beberapa jenis header, tetapi datanya harus seragam.
▢ Saat menambahkan header, Anda membuat subclass RecyclerView untuk menyisipkan header ke posisi yang tepat.
Pertanyaan 2
Kapan sebaiknya Anda menggunakan coroutine dengan RecyclerView? Pilih semua pernyataan yang benar.
▢ Tidak pernah. RecyclerView adalah elemen UI dan tidak boleh menggunakan coroutine.
▢ Gunakan coroutine untuk tugas yang berjalan lama yang dapat memperlambat UI.
▢ Manipulasi daftar dapat memakan waktu lama, dan Anda harus selalu melakukannya menggunakan coroutine.
▢ Gunakan coroutine dengan fungsi penangguhan untuk menghindari pemblokiran thread utama.
Pertanyaan 3
Manakah dari berikut ini yang TIDAK harus Anda lakukan saat menggunakan lebih dari satu ViewHolder?
▢ Di ViewHolder, berikan beberapa file tata letak untuk di-inflate sesuai kebutuhan.
▢ Di onCreateViewHolder(), tampilkan jenis penampung tampilan yang benar untuk item data.
▢ Di onBindViewHolder(), hanya ikat data jika holder tampilan adalah jenis holder tampilan yang benar untuk item data.
▢ Menggeneralisasi tanda tangan class adaptor untuk menerima RecyclerView.ViewHolder apa pun.
Mulai pelajaran berikutnya:
Untuk link ke codelab lain dalam kursus ini, lihat halaman landing codelab Dasar-Dasar Android Kotlin.


