Halo kabeh. Aku mutusake kanggo ngunggahake topik dhaptar kompleks ing Android. Topik iki saya bingung ing wiwitan. Iku ketoke angel banget kanggo kula, nanging kabeh iku bener prasaja. Lan aku mikir yen sapa wae sing saiki ngadhepi masalah iki bakal nemokake artikel kasebut migunani. Kabeh conto ditulis ing Kotlin. Aku nyoba nulis komentar ing endi-endi lan nggawe kanthi jelas kanggo sing nulis nganggo basa Jawa. Dadi iki struktur kelas kita:
MainActivity
- Iki kelas utama. ListAdapter
- kene kita njiret dhaftar kita kanggo ndeleng unsur. ViewHolder
(ana sawetara) - iki markup kita kanggo saben jinis unsur. Kelas item minangka data kanggo dhaptar (pojo). File Xml minangka markup ( activity_main
kanggo layar utama, liyane kanggo saben jinis unsur dhaptar). Wangsulan: Bab ingkang sapisanan kudu kita tindakake iku nambah dependensi supaya kita bisa nggunakake dhaptar (saperangan alesan iki ketergantungan ilang nalika nggawe proyek). Ing file build.gradle
ing blok dependencies
kita nambah baris:
implementation "androidx.recyclerview:recyclerview:1.1.0"
Sabanjure, kita nemokake file kasebut activity_main.xml
: Ing kene mung ana unsur kanggo dhaptar - RecyclerView
. Kita nambah indents lan format kanggo rasa. Sabanjure kita nggawe kelas BaseViewHolder
. Iku bakal abstrak, amarga kabeh kelas kita bakal oleh warisan saka iku ViewHolder
. Iku cukup bisa kanggo nindakake tanpa iku, nanging aku isih menehi saran manawa kabeh kelas warisan padha ing struktur. (ing Kotlin, extends
lan implement
digunakake tinimbang :
. Kaya ing Jawa, kene sampeyan bisa oleh warisan saka siji kelas lan ngleksanakake akeh antarmuka) Sabanjure, kita nggawe kelas data kita: ItemTitle
, ItemGoogle
lan ItemApple
. Ing Jawa, iki minangka kelas biasa kanthi konstruktor lan getter.
data class ItemGoogle(
val name: String,
val product: String,
val version: String,
val isUse: Boolean
) : ListMarker
data class ItemApple(
val name: String,
val country: String,
val year: Int
) : ListMarker
data class ItemTitle(
val title: String,
val amount: Int
) : ListMarker
Ayo dadi fokus ing antarmuka kene ListMarker
.
//это наш маркер. Его должны реализовать все айтемы, которые будут
//отображаться в конечном итоге в списке
interface ListMarker
Kabeh kelas data kita ngleksanakake antarmuka iki kanggo matak menyang jinis padha. Kita bakal mbutuhake iki mengko kanggo dhaptar kita. Sabanjure, kita nggawe viewholders kanggo saben unsur dhaptar. Aja lali yen saben wong duwe warisan saka BaseViewHolder
. Ing kelas kasebut, kita nemtokake unsur view
sing cocog karo lapangan saka kelas data.
abstract class BaseViewHolder<t>(itemView: View) : RecyclerView.ViewHolder(itemView) {
abstract fun bind(item: T)
}
class GoogleViewHolder(view: View) : BaseViewHolder<itemgoogle>(view) {
override fun bind(item: ItemGoogle) {
itemView.tvName.text = item.name
itemView.tvProduct.text = item.product
itemView.tvVersion.text = item.version
if (item.isUse) {
itemView.tvProduct.visibility = View.GONE
} else {
itemView.tvProduct.visibility = View.VISIBLE
}
}
}
class AppleViewHolder(view: View) : BaseViewHolder<itemapple>(view) {
override fun bind(item: ItemApple) {
//можем делать так
itemView.tvName.text = item.name
itemView.tvCountry.text = item.country
itemView.tvYear.text = item.year.toString()
/*----сверху и снизу два идентичных блока----*/
//а можем сделать такой блок и не использовать в каждой строке itemView
with(itemView) {
tvName.text = item.name
tvCountry.text = item.country
tvYear.text = item.year.toString()
}
}
}
class TitleViewHolder(view: View) : BaseViewHolder<itemtitle>(view) {
override fun bind(item: ItemTitle) {
itemView.tvTitle.text = item.title
itemView.tvAmount.text = item.amount.toString()
}
}
Ora ana logika tartamtu ing kene, kabeh gampang banget. Uga, saben viewholder duwe tata letak dhewe: list_item_title.xml
list_item_google.xml
list_item_apple.xml
Saiki ayo pindhah menyang bagean sing paling angel - ListAdapter
.
class ListAdapter : RecyclerView.Adapter<baseviewholder<*>>() {
companion object {
//задаем константы для каждого типа айтема
private const val TYPE_TITLE = 0
private const val TYPE_GOOGLE = 1
private const val TYPE_APPLE = 2
}
//здесь можно использовать обычный ArrayList
//сюда добавляются все айтемы, которые реализовали интерфейс ListMarker
//How вариант можно было сделать mutableListOf<any>() и обойтись без интерфейса
private val items = mutableListOf<listmarker>()
internal fun swapData(list: List<listmarker>) {
items.clear()
items.addAll(list)
notifyDataSetChanged()
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BaseViewHolder<*> {
return when(viewType) {
//задаем разметку для каждого типа айтема
TYPE_TITLE -> TitleViewHolder(parent.inflate(R.layout.list_item_title))
TYPE_GOOGLE -> GoogleViewHolder(parent.inflate(R.layout.list_item_google))
TYPE_APPLE -> AppleViewHolder(parent.inflate(R.layout.list_item_apple))
else -> throw IllegalArgumentException("Invalid view type")
}
}
override fun getItemViewType(position: Int): Int {
return when (items[position]) {
is ItemTitle -> TYPE_TITLE
is ItemGoogle -> TYPE_GOOGLE
is ItemApple -> TYPE_APPLE
else -> throw IllegalArgumentException("Invalid type of item $position")
}
}
override fun getItemCount(): Int {
//этот метод определяет размер списка
return items.size
}
override fun onBindViewHolder(holder: BaseViewHolder<*>, position: Int) {
val element = items[position]
when (holder) {
//отправляем каждый айтем к своему ViewHolder
is TitleViewHolder -> holder.bind(element as ItemTitle)
is GoogleViewHolder -> holder.bind(element as ItemGoogle)
is AppleViewHolder -> holder.bind(element as ItemApple)
else -> throw IllegalArgumentException()
}
}
}
</listmarker></listmarker></any></baseviewholder<*>
Nalika kita marisi kelas kita saka RecyclerView.Adapter
, kita kudu ngilangi telung cara: onCreateViewHolder
, getItemCount
, onBindViewHolder
. onCreateViewHolder
- ing kene kita miwiti kelas kita ViewHolder
. getItemCount
- cara iki tanggung jawab kanggo ukuran dhaftar. onBindViewHolder
- ing kene kita ngirim unsur dhaptar menyang kelas kita ViewHolder
. Kanggo dhaptar biasa kanthi siji jinis unsur, iki bakal cukup. Nanging kanggo macem-macem jinis kita isih kudu redefine cara getItemViewType
(kanggo iki kita nggunakake konstanta ing ndhuwur kelas adaptor kita. Ing Jawa sampeyan bisa nggunakake final
variabel kanggo iki). Uga ing Jawa, onBindViewHolder
tinimbang ekspresi, when
sampeyan bisa nggunakake if
. Lan pungkasane, ayo pindhah menyang kelas utama - MainActivity
.
class MainActivity : AppCompatActivity() {
private val adapter = ListAdapter()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
initRecyclerView()
}
private fun initRecyclerView() {
rvList.layoutManager = LinearLayoutManager(this)
//здесь мы задаем разделитель между айтемами, чтоб они не сливались друг с другом
val divider = DividerItemDecoration(this, LinearLayoutManager.VERTICAL)
rvList.addItemDecoration(divider)
rvList.adapter = adapter
stubData()
}
private fun stubData() {
val list = mutableListOf<listmarker>()
list.add(ItemTitle("title1", 4))
list.add(ItemGoogle("android", "product1", "17.0v", true))
list.add(ItemGoogle("no name", "product2", "3.1v", false))
list.add(ItemApple("macOs", "USA", 2005))
list.add(ItemApple("iOs", "China", 2007))
list.add(ItemTitle("title2", 2))
list.add(ItemGoogle("map", "product3", "23.0v", true))
list.add(ItemApple("car", "England", 2018))
list.add(ItemTitle("title3", 0))
//отправляем все данные в адаптер
adapter.swapData(list)
}
}
</listmarker>
RecyclerView
Iki ngendi kita lan iki initialized ListAdapter
. Iki minangka kode boilerplate sing akeh ditemoni. Ing cara, stubData
aku ngisi dhaptar data (kaya sing sampeyan deleng, data karo unsur sing beda) lan ngirim dhaptar iki menyang adaptor. Sabanjure, kita miwiti aplikasi kita, lan kita kudu ndeleng kaya iki ing layar: Kaya sing sampeyan ngerteni, ana macem-macem unsur ing siji dhaptar, sing dadi tujuane. PS Kelalen nyebutake Extension
. Iki katon kaya:
//это расширение для класса ViewGroup. Теперь мы можем по всему проекту использовать
//короткое inflate instead of длинного LayoutInflater.from(context).inflate
fun ViewGroup.inflate(@LayoutRes layoutRes: Int, attachToRoot: Boolean = false): View =
LayoutInflater.from(context).inflate(layoutRes, this, attachToRoot)
Iki dudu kelas, nanging file, mulane ora ana jeneng ing njero. Nanging saiki kita bisa nggunakake ing adaptor mung inflate
tinimbang struktur dawa. Sayange, iki ora kasedhiya kanggo Jawa, supaya nulis LayoutInflater.from(context).inflate
. Mekaten. Nganti mbesuk :) Link menyang GitHub
GO TO FULL VERSION