Menerapkan Multitype View pada RecyclerView
Hallo teman-teman developer!
Akhirnya, setelah hampir sebulan tidak menulis, senang bisa berbagi lagi di blog Dicoding 🙂
💻 Mulai Belajar Pemrograman
Belajar pemrograman di Dicoding Academy dan mulai perjalanan Anda sebagai developer profesional.
Daftar SekarangKali ini saya akan sedikit berbagi tutorial mengenai salah satu mekanisme yang bisa kita terapkan pada RecyclerView yaitu Multitype View.
Sebelum kita menerapkannya, mari kita ketahui terlebih dahulu apa itu yang dimaksud dengan Multitype View. Seperti namanya, Multitype View merupakan sebuah cara agar kita bisa menampilkan dua atau lebih tipe view di dalam sebuah RecyclerView. Nah, contoh penerapannya adalah seperti berikut:
Pada contoh aplikasi di atas terdapat 2 (dua) tipe view yang ditampilkan pada RecyclerView. Pertama, sebuah view yang hanya menampilkan TextView dan yang kedua adalah view yang menampilkan ImageView beserta beberapa TextView dan Button, Menarik bukan? Mari kita buat aplikasi seperti yang dicontohkan di atas.
Project baru dan persiapan
Oke, langsung saja buat project baru pada Android Studio. Setelah selesai, tambahkan dependensi RecyclerView dan Material seperti berikut:
1 2 3 4 5 |
dependencies { ... implementation 'androidx.recyclerview:recyclerview:1.0.0' implementation 'com.google.android.material:material:1.1.0-alpha07' } |
Biar lebih mirip seperti pada contoh, ubahlah parent dari style AppTheme pada berkas styles.xml menjadi seperti berikut:
1 2 3 4 5 6 |
<resources> <!-- Base application theme. --> <style name="AppTheme" parent="Theme.MaterialComponents.Light.NoActionBar"> ... </style> </resources> |
Maksud perubahan di atas adalah untuk menghilangkan Toolbar bawaan. Lanjut, buka berkas colors.xml kemudian ubahlah beberapa nilai color-nya menjadi seperti berikut:
1 2 3 4 5 |
<resources> <color name="colorPrimary">#FFFFFF</color> <color name="colorPrimaryDark">#C0C0C0</color> <color name="colorAccent">#4CAF50</color> </resources> |
Terakhir, tambahkan komponen widget RecyclerView pada berkas activity_main.xml, sehingga kodenya akan menjadi seperti berikut:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity"> <androidx.recyclerview.widget.RecyclerView android:paddingTop="20dp" android:paddingBottom="30dp" android:clipToPadding="false" android:id="@+id/menuItem" android:layout_width="match_parent" android:layout_height="0dp" app:layout_constraintTop_toTopOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent"/> </androidx.constraintlayout.widget.ConstraintLayout> |
Pembuatan Data Class
Sebelum membuat kelas ViewHolder dan Adapter yang menjadi bagian penting dari tutorial kali ini, mari kita buat terlebih dahulu data class MenuItem seperti berikut:
1 |
data class MenuItem(val name: String, val price: Int, var count: Int, val itemPreview: Int) |
Pada data class tersebut terdapat beberapa properti yang akan menampung data yang nantinya akan ditampilkan pada RecyclerView. Selesai, mari kita lanjutkan ke pembuatan ViewHolder.
Pembuatan kelas ViewHolder
Karena kita akan membuat 2 (dua) tipe view, maka kita juga akan membuat 2 (dua) kelas ViewHolder. Tapi sebelumnya mari kita siapkan terlebih dahulu file layout untuk masing-masing ViewHolder tersebut. Buat layout baru dengan nama item_header.xml kemudian ubah menjadi seperti berikut:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
<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"> <TextView tools:text="Minuman" android:layout_width="wrap_content" android:layout_height="wrap_content" android:id="@+id/tvHeaderItem" app:layout_constraintTop_toTopOf="parent" app:layout_constraintStart_toStartOf="parent" android:layout_marginStart="24dp" android:layout_marginTop="16dp" app:layout_constraintBottom_toBottomOf="parent" android:layout_marginBottom="16dp" android:textSize="24sp" android:textStyle="bold" android:textColor="#313131"/> </androidx.constraintlayout.widget.ConstraintLayout> |
Layout di atas akan kita gunakan untuk menampilkan teks Makanan dan Minuman seperti yang dicontohkan di awal. Lanjut, buat lagi layout baru dengan nama item_menu.xml. Kemudian ubahlah kode nya menjadi seperti berikut:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 |
<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:layout_width="50dp" android:layout_height="50dp" app:srcCompat="@drawable/breakfast" app:layout_constraintTop_toTopOf="parent" app:layout_constraintStart_toStartOf="parent" android:layout_marginTop="16dp" android:layout_marginStart="24dp" android:id="@+id/itemPreview" app:layout_constraintBottom_toBottomOf="parent" android:layout_marginBottom="16dp"/> <TextView tools:text="Nasi goreng pedas" android:layout_width="wrap_content" android:layout_height="wrap_content" app:layout_constraintTop_toTopOf="@+id/itemPreview" android:id="@+id/itemName" android:layout_marginStart="24dp" app:layout_constraintStart_toEndOf="@+id/itemPreview" android:textSize="14sp" android:textStyle="normal" android:textColor="@android:color/primary_text_light"/> <TextView tools:text="Rp. 20.0000" android:layout_width="wrap_content" android:layout_height="wrap_content" android:id="@+id/itemPrice" android:layout_marginTop="4dp" app:layout_constraintTop_toBottomOf="@+id/itemName" app:layout_constraintStart_toStartOf="@+id/itemName" android:textSize="12sp"/> <com.google.android.material.button.MaterialButton android:text="+" android:gravity="center_vertical|center_horizontal" android:layout_width="30dp" android:layout_height="40dp" android:layout_marginEnd="16dp" android:id="@+id/itemAdd" app:backgroundTint="@color/colorAccent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintBottom_toBottomOf="parent" style="@style/Widget.MaterialComponents.Button.TextButton" android:layout_marginBottom="8dp"/> <TextView tools:text="0" android:layout_width="wrap_content" android:layout_height="wrap_content" app:layout_constraintBottom_toBottomOf="@+id/itemAdd" android:id="@+id/itemCount" android:textSize="17sp" app:layout_constraintTop_toTopOf="@+id/itemAdd" app:layout_constraintEnd_toStartOf="@+id/itemAdd" android:layout_marginEnd="16dp"/> <com.google.android.material.button.MaterialButton android:text="-" android:gravity="center_vertical|center_horizontal" android:layout_width="30dp" android:layout_height="40dp" android:layout_marginEnd="16dp" android:id="@+id/itemRemove" app:backgroundTint="@color/colorAccent" app:layout_constraintBottom_toBottomOf="parent" style="@style/Widget.MaterialComponents.Button.TextButton" android:layout_marginBottom="8dp" app:layout_constraintEnd_toStartOf="@+id/itemCount"/> </androidx.constraintlayout.widget.ConstraintLayout> |
Kita akan melihat kode eror di attribute srcCompat pada widget ImageView. Yuk ganti dengan berkas gambar yang diinginkan. Jika mau disamakan, berkas gambarnya bisa kamu unduh di laman flat icon ini.
Selanjutnya, mari kita buat kelas ViewHolder. Langsung saja buat kelas baru dengan nama MenuHeaderHolder kemudian tambahkan beberapa baris kode di dalamnya sehingga menjadi seperti berikut:
1 2 3 4 5 6 7 |
class MenuHeaderHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { private val itemHeader = itemView.findViewById(R.id.tvHeaderItem) as TextView fun bindContent(text: String){ itemHeader.text = text } } |
Kelas ViewHolder di atas akan kita gunakan untuk menampilkan satu informasi, sehingga di dalamnya hanya terdapat satu TextView. Kemudian buat lagi kelas baru dengan nama MenuItemHolder yang akan kita gunakan sebagai ViewHolder lainnya. Buka, kemudian ubahlah menjadi seperti di bawah ini:
1 2 3 4 5 6 7 8 9 10 11 |
class MenuItemHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { private val itemName = itemView.findViewById(R.id.itemName) as TextView private val itemPrice = itemView.findViewById(R.id.itemPrice) as TextView private val itemCount = itemView.findViewById(R.id.itemCount) as TextView fun bindContent(menuItem: MenuItem){ itemCount.text = menuItem.count.toString() itemName.text = menuItem.name itemPrice.text = "Rp. ${menuItem.price}" } } |
Sama seperti kelas ViewHolder sebelumnya, pada kelas ViewHolder di atas terdapat beberapa view dan satu fungsi yang akan kita gunakan untuk menetapkan nilai dari view yang sudah dideklarasikan.
Pembuatan kelas Adapter
Selanjutnya adalah pembuatan Adapter, tahap ini adalah fokus utama ketika kita ingin menerapkan Multitype View, karena disinilah kita akan mengatur view berdasarkan data yang ingin ditampilkan. Mari kita mulai dengan membuat kelas baru yang mewarisi kelas RecyclerView.Adapter seperti berikut:
1 2 |
class MenuAdapter(private val data: List<Any>) : RecyclerView.Adapter<RecyclerView.ViewHolder>() { } |
Adapter di atas memiliki 1 (satu) properti pada constructor yaitu data dengan tipe List<Any>, kenapa menggunakan Any? Agar kita bisa memasukkan nilai dengan tipe apapun ketika ingin membuat instance dari adapter di atas, Lanjut, mari kita override beberapa fungsi yang menjadi bagian dari RecyclerView.Adapter seperti halnya ketika kita ingin membuat RecyclerView pada umumnya:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
class MenuAdapter(private val data: List<Any>) : RecyclerView.Adapter<RecyclerView.ViewHolder>() { override fun getItemViewType(position: Int): Int { } override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { } override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { } override fun getItemCount(){ } } |
Kemudian tambahkan 2 (dua) variabel statis yang akan kita gunakan sebagai tag untuk membedakan view yang akan ditampilkan.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
class MenuAdapter(private val data: List<Any>) : RecyclerView.Adapter<RecyclerView.ViewHolder>() { companion object { private const val ITEM_HEADER = 0 private const val ITEM_MENU = 1 } override fun getItemViewType(position: Int): Int { } override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { } override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { } override fun getItemCount() { } } |
Selanjutnya, kita akan mengubah satu persatu fungsi yang sebelumnya sudah kita override. Dimulai dari fungsi getItemViewType, mari kita mengubahnya jadi seperti berikut:
1 2 3 4 5 6 7 |
override fun getItemViewType(position: Int): Int { return when (data[position]) { is String -> ITEM_HEADER is MenuItem -> ITEM_MENU else -> throw IllegalArgumentException("Undefined view type") } } |
Kondisi di dalam fungsi pada kode di atas bertujuan untuk mengecek instance dari tiap item berdasarkan posisinya, inilah kenapa kita menggunakan kelas Any sebagai tipe argumen pada tipe data List untuk properti data. Lanjut ke fungsi onCreateViewHolder, ubahlah menjadi seperti berikut:
1 2 3 4 5 6 7 |
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { return when (viewType) { ITEM_HEADER -> MenuHeaderHolder(parent.inflate(R.layout.item_header)) ITEM_MENU -> MenuItemHolder(parent.inflate(R.layout.item_menu)) else -> throw throw IllegalArgumentException("Undefined view type") } } |
Berbeda dengan fungsi sebelumnya yang menggunakan instance dari item yang akan digunakan sebagai ekspresi, pada fungsi di atas kita bisa langsung menggunakan tag yang sudah kita tambahkan di awal yang dikomparasi dengan parameter viewType sebagai ekspresi. Berikutnya fungsi onBindViewHolder. Ubahlah kode dari fungsi tersebut menjadi seperti berikut:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { when (holder.itemViewType) { ITEM_HEADER -> { val headerHolder = holder as MenuHeaderHolder headerHolder.bindContent(data[position] as String) } ITEM_MENU -> { val itemHolder = holder as MenuItemHolder itemHolder.bindContent(data[position] as MenuItem) } else -> throw IllegalArgumentException("Undefined view type") } } |
Fungsi di atas menggunakan ekspresi yang sama seperti pada fungsi sebelumnya dan di dalam setiap blok kondisi yang ada pada fungsi tersebut terdapat casting ViewHolder yang sebelumnya sudah kita buat, ini dimaksudkan agar kita tidak salah menampilkan konten pada RecyclerView. Terakhir, ubah fungsi getItemCount menjadi seperti berikut:
1 |
override fun getItemCount() = data.size |
Sampai di sini kita sudah selesai dengan pembuatan adapter. Kode keseluruhan dari adapter yang sudah kita buat kurang lebih akan seperti berikut:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
class MenuAdapter(private val data: List<Any>) : RecyclerView.Adapter<RecyclerView.ViewHolder>() { companion object { private const val ITEM_HEADER = 0 private const val ITEM_MENU = 1 } override fun getItemViewType(position: Int): Int { return when (data[position]) { is String -> ITEM_HEADER is MenuItem -> ITEM_MENU else -> throw IllegalArgumentException("Undefined view type") } } override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { return when (viewType) { ITEM_HEADER -> MenuHeaderHolder(parent.inflate(R.layout.item_header)) ITEM_MENU -> MenuItemHolder(parent.inflate(R.layout.item_menu)) else -> throw throw IllegalArgumentException("Undefined view type") } } override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { when (holder.itemViewType) { ITEM_HEADER -> { val headerHolder = holder as MenuHeaderHolder headerHolder.bindContent(data[position] as String) } ITEM_MENU -> { val itemHolder = holder as MenuItemHolder itemHolder.bindContent(data[position] as MenuItem) } else -> throw IllegalArgumentException("Undefined view type") } } override fun getItemCount() = data.size } |
Inialisasi data
Sebelum memodifikasi kelas MainActivity, mari kita inisialisasi terlebih dahulu data yang nantinya akan kita tampilkan. Oke, buat kelas baru dengan nama Menu kemudian tambahkan beberapa baris kode di dalamnya seperti berikut:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
class Menu { companion object { val menus: List<Any> get() = mutableListOf( "Makanan", MenuItem("Nasi Goreng 1", 20000, 0, R.drawable.breakfast), MenuItem("Nasi Goreng 2", 30000, 0, R.drawable.breakfast), MenuItem("Nasi Goreng 3", 40000, 0, R.drawable.breakfast), MenuItem("Nasi Goreng 4", 15000, 0, R.drawable.breakfast), MenuItem("Nasi Goreng 5", 100000, 0, R.drawable.breakfast), MenuItem("Nasi Goreng 6", 12000, 0, R.drawable.breakfast), "Minuman", MenuItem("Es Jeruk 1", 6000, 0, R.drawable.breakfast), MenuItem("Es Jeruk 1", 7000, 0, R.drawable.breakfast), MenuItem("Es Jeruk 1", 4000, 0, R.drawable.breakfast), MenuItem("Es Jeruk 1", 5000, 0, R.drawable.breakfast), MenuItem("Es Jeruk 1", 7000, 0, R.drawable.breakfast), MenuItem("Es Jeruk 1", 8000, 0, R.drawable.breakfast), MenuItem("Es Jeruk 1", 9000, 0, R.drawable.breakfast) ) } } |
Modifikasi MainActivity dan Menjalankan Aplikasi
Terakhir, mari kita modifikasi kelas MainActivity dengan menambahkan variabel dan mengubah kode pada fungsi onCreate seperti berikut:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
class MainActivity : AppCompatActivity() { private var menuData = listOf<Any>() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) menuData = Menu.menus val menuAdapter = MenuAdapter(menuData) menuItem.hasFixedSize() menuItem.layoutManager = LinearLayoutManager(this) menuItem.adapter = menuAdapter } } |
Kode di atas terdapat penambah satu variabel dengan tipe List<Any>. Kemudian di dalam fungsi onCreate terdapat pembuatan instance dari kelas adapter dan inisialisasi beberapa properti untuk RecyclerView termasuk menetapkan adapter-nya.
Jika dirasa sudah selesai, kamu bisa coba menjalankannya pada emulator atau device. Jika tidak ada eror, hasilnya akan seperti berikut:
Kesimpulan
Multitype View adalah salah satu mekanisme yang dapat digunakan untuk meningkatkan performa aplikasi yang kita kembangkan. Kenapa? Karena di beberapa kasus yang sering ditemukan, untuk membuat aplikasi yang serupa, di dalam satu berkas layout bisa terdapat 2 (dua) atau lebih RecyclerView. Alhasil, aplikasi membutuhkan resource lebih untuk melakukan render dan tampil pada layar device. Kasus seperti ini tidak akan terlihat jika data yang ditampilkan sedikit. Namun jika data yang akan ditampilkan memiliki jumlah yang banyak, kita akan merasakan perbedaannya. Dengan data yang sedikit pun kita disarankan untuk menerapkan Multitype View daripada menggunakan 2 (dua) atau lebih RecyclerView di dalam sebuah berkas layout.
Jika masih belum terlalu familiar dengan RecyclerView, teman-teman bisa mempelajarinya di kelas Menjadi Android Developer Expert dan Memulai Pemrograman Dengan Kotlin untuk yang baru atau ingin memperdalam pengetahuan tentang Kotlin, bahasa yang kita gunakan pada tutorial kali ini.
Sampai disini dulu ya tutorial kali ini. Ada yang kurang jelas? Yuk tulis di kolom komentar. Untuk yang mau melihat kode sumber dari project yang sudah kita buat, silakan cek di laman Github ini.
Menerapkan Multitype View pada RecyclerView-end
Artikel Terkait Lainnya
https://www.dicoding.com/blog/recyclerview-frequently-asked-question/
https://www.dicoding.com/blog/kenal-lebih-dekat-dengan-constraintlayout/
Suka dengan Materi ini?
Jika ya, ini merupakan salah satu materi fundamental yang mengantarmu ke kelas berikut ini: