Hai semua. Saya memutuskan untuk membangkitkan topik senarai kompleks dalam Android. Topik ini membingungkan saya pada mulanya. Ia kelihatan sangat sukar bagi saya, tetapi semuanya sebenarnya lebih mudah. Dan saya fikir sesiapa yang kini berhadapan dengan masalah ini akan mendapati artikel itu berguna. Semua contoh ditulis dalam Kotlin. Saya cuba menulis komen di mana-mana dan menjadikannya sejelas mungkin bagi mereka yang menulis dalam Java. Jadi inilah struktur kelas kami:
MainActivity
- Ini adalah kelas utama. ListAdapter
- di sini kami mengikat senarai kami untuk melihat elemen. ViewHolder
(terdapat beberapa daripadanya) - ini ialah penanda kami untuk setiap jenis elemen. Kelas item ialah data untuk senarai (pojo). Fail Xml ialah penanda ( activity_main
untuk skrin utama, selebihnya untuk setiap jenis elemen senarai). Perkara pertama yang perlu kita lakukan ialah menambah kebergantungan supaya kita boleh menggunakan senarai (atas sebab tertentu kebergantungan ini hilang semasa membuat projek). Dalam fail build.gradle
dalam blok dependencies
kami menambah baris:
implementation "androidx.recyclerview:recyclerview:1.1.0"
Seterusnya kami mencari fail activity_main.xml
: Di sini hanya terdapat elemen kami untuk senarai - RecyclerView
. Kami menambah sebarang inden dan pemformatan secukup rasa. Seterusnya kita buat kelas BaseViewHolder
. Ia akan menjadi abstrak, kerana semua kelas kami akan mewarisi daripadanya ViewHolder
. Ia agak mustahil untuk dilakukan tanpa itu, tetapi saya masih mengesyorkan memastikan bahawa semua kelas pewarisan adalah sama dalam struktur. (dalam Kotlin, extends
dan sebaliknya implement
digunakan :
. Seperti dalam Java, di sini anda boleh mewarisi daripada satu kelas dan melaksanakan banyak antara muka) Seterusnya, kami mencipta kelas data kami: ItemTitle
, ItemGoogle
dan ItemApple
. Di Jawa, ini adalah kelas biasa dengan pembina dan pengambil.
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
Mari fokus pada antara muka di sini ListMarker
.
//это наш маркер. Его должны реализовать все айтемы, которые будут
//отображаться в конечном итоге в списке
interface ListMarker
Semua kelas data kami melaksanakan antara muka ini untuk menghantarnya kepada jenis yang sama. Kami akan memerlukan ini kemudian untuk senarai kami. Seterusnya, kami mencipta pemegang paparan kami untuk setiap elemen senarai. Jangan lupa bahawa setiap daripada mereka mewarisi daripada BaseViewHolder
. Dalam kelas ini kami menentukan elemen yang view
sepadan dengan medan daripada 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()
}
}
Tidak ada logik khusus di sini, semuanya sangat mudah. Selain itu, setiap pemegang paparan mempunyai reka letak sendiri: list_item_title.xml
list_item_google.xml
list_item_apple.xml
Sekarang mari kita beralih ke bahagian yang paling sukar - 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<*>
Apabila kami mewarisi kelas kami daripada RecyclerView.Adapter
, kami perlu mengatasi tiga kaedah: onCreateViewHolder
, getItemCount
, onBindViewHolder
. onCreateViewHolder
- di sini kami memulakan kelas kami ViewHolder
. getItemCount
— kaedah ini bertanggungjawab untuk saiz senarai. onBindViewHolder
- di sini kami menghantar elemen senarai ke kelas kami ViewHolder
. Untuk senarai biasa dengan satu jenis elemen, ini sudah memadai. Tetapi untuk jenis yang berbeza kami masih perlu mentakrifkan semula kaedah getItemViewType
(untuk ini kami menggunakan pemalar yang berada di bahagian atas kelas penyesuai kami. Di Java anda boleh menggunakan final
pembolehubah untuk ini). Juga dalam Java, onBindViewHolder
bukannya ungkapan, when
anda boleh menggunakan if
. Dan akhirnya, mari kita beralih ke kelas utama kita - 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
Di sinilah kami dan dimulakan ListAdapter
. Ini adalah kod boilerplate yang cantik yang banyak ditemui. Dalam kaedah itu, stubData
saya mengisi senarai dengan data (seperti yang anda lihat, data dengan elemen yang berbeza) dan menyerahkan senarai ini kepada penyesuai. Seterusnya, kami melancarkan aplikasi kami, dan kami akan melihat sesuatu seperti ini pada skrin kami: Seperti yang anda lihat, terdapat elemen berbeza dalam satu senarai, yang merupakan matlamat kami. PS Terlupa untuk menyebut Extension
. Inilah rupanya:
//это расширение для класса 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)
Ini bukan kelas, tetapi fail, itulah sebabnya tiada nama di dalamnya. Tetapi kini kita boleh menggunakannya dalam penyesuai semata-mata inflate
dan bukannya struktur panjang. Malangnya, ini tidak disediakan untuk Java, jadi tulis LayoutInflater.from(context).inflate
. Itu sahaja. Sehingga lain kali :) Pautan ke GitHub
GO TO FULL VERSION