Kamusta kayong lahat. Nagpasya akong itaas ang paksa ng mga kumplikadong listahan sa Android. Ang paksang ito ay naging palaisipan sa akin noong una. Tila napakahirap para sa akin, ngunit ang lahat ay talagang mas simple. At sa palagay ko ang sinumang nahaharap ngayon sa problemang ito ay magiging kapaki-pakinabang ang artikulo. Ang lahat ng mga halimbawa ay nakasulat sa Kotlin. Sinubukan kong magsulat ng mga komento sa lahat ng dako at gawin itong malinaw hangga't maaari para sa mga nagsusulat sa Java. Kaya narito ang aming istraktura ng klase:
MainActivity
- Ito ang pangunahing klase. ListAdapter
- dito namin itali ang aming listahan upang tingnan ang mga elemento. ViewHolder
(may ilan sa kanila) - ito ang aming markup para sa bawat uri ng elemento. Ang mga klase ng item ay data para sa isang listahan (pojo). Ang mga Xml file ay mga markup ( activity_main
para sa pangunahing screen, ang natitira para sa bawat uri ng elemento ng listahan). Ang unang bagay na kailangan nating gawin ay magdagdag ng dependency upang magamit natin ang mga listahan (sa ilang kadahilanan ay nawawala ang dependency na ito kapag lumilikha ng isang proyekto). Sa file build.gradle
sa block dependencies
idagdag namin ang linya:
implementation "androidx.recyclerview:recyclerview:1.1.0"
Susunod na hanapin namin ang file activity_main.xml
: Narito lamang ang aming elemento para sa mga listahan - RecyclerView
. Nagdaragdag kami ng anumang mga indent at pag-format sa panlasa. Susunod na gagawin namin ang klase BaseViewHolder
. Ito ay magiging abstract, dahil ang lahat ng aming mga klase ay magmamana mula dito ViewHolder
. Ito ay lubos na posible na gawin nang wala ito, ngunit inirerekomenda ko pa rin na tiyakin na ang lahat ng pagmamana ng mga klase ay pareho sa istraktura. (sa Kotlin, extends
at implement
ginagamit sa halip na :
. Tulad ng sa Java, dito maaari kang magmana mula sa isang klase at magpatupad ng maraming mga interface) Susunod, lumikha kami ng aming mga klase ng data: ItemTitle
, ItemGoogle
at ItemApple
. Sa Java, ito ay mga ordinaryong klase na may constructor at 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
Tumutok tayo sa interface dito ListMarker
.
//это наш маркер. Его должны реализовать все айтемы, которые будут
//отображаться в конечном итоге в списке
interface ListMarker
Ang lahat ng aming mga klase ng data ay nagpapatupad ng interface na ito upang i-cast ang mga ito sa parehong uri. Kakailanganin namin ito mamaya para sa aming listahan. Susunod, gagawa kami ng aming mga viewholder para sa bawat elemento ng listahan. Huwag kalimutan na ang bawat isa sa kanila ay nagmamana mula sa BaseViewHolder
. Sa mga klaseng ito, tinukoy namin kung aling elemento view
ang tumutugma sa field mula sa mga klase ng 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()
}
}
Walang partikular na lohika dito, ang lahat ay sobrang simple. Gayundin, ang bawat viewholder ay may sariling layout: list_item_title.xml
list_item_google.xml
list_item_apple.xml
Ngayon ay lumipat tayo sa pinakamahirap na bahagi - 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<*>
Kapag minana namin ang aming klase mula sa RecyclerView.Adapter
, kakailanganin naming i-override ang tatlong pamamaraan: onCreateViewHolder
, getItemCount
, onBindViewHolder
. onCreateViewHolder
- dito natin sinisimulan ang ating mga klase ViewHolder
. getItemCount
— ang pamamaraang ito ay responsable para sa laki ng listahan. onBindViewHolder
- dito ipinapasa namin ang mga elemento ng listahan sa aming mga klase ViewHolder
. Para sa isang regular na listahan na may isang uri ng elemento, ito ay sapat na. Ngunit para sa iba't ibang uri kailangan pa rin naming muling tukuyin ang pamamaraan getItemViewType
(para dito ginagamit namin ang mga constant na nasa tuktok ng aming klase ng adaptor. Sa Java maaari kang gumamit ng final
mga variable para dito). Gayundin sa Java, onBindViewHolder
sa halip na isang expression, when
maaari kang gumamit ng isang regular na if
. At sa wakas, lumipat tayo sa aming pangunahing klase - 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>
Dito nasimulan ang ating RecyclerView
at ListAdapter
. Ito ay isang magandang boilerplate code na naranasan ng marami. Sa pamamaraan, stubData
pinunan ko ang listahan ng data (tulad ng nakikita mo, data na may iba't ibang elemento) at ipinasa ang listahang ito sa adaptor. Susunod, ilulunsad namin ang aming application, at dapat naming makita ang isang bagay na tulad nito sa aming screen: Gaya ng nakikita mo, may iba't ibang elemento sa isang listahan, na aming layunin. PS Nakalimutang banggitin ang Extension
. Ito ang hitsura nito:
//это расширение для класса 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)
Ito ay hindi isang klase, ngunit isang file, kaya naman walang pangalan sa loob nito. Ngunit ngayon ay maaari na nating gamitin ito sa adapter inflate
sa halip na isang mahabang istraktura. Sa kasamaang palad, hindi ito ibinigay para sa Java, kaya isulat ang LayoutInflater.from(context).inflate
. Iyon lang. Hanggang sa susunod :) Link sa GitHub
GO TO FULL VERSION