大家好。我決定提出 Android 中複雜清單的主題。這個話題一開始讓我很困惑。這對我來說似乎很難,但實際上一切都更簡單。我認為現在面臨這個問題的任何人都會發現這篇文章很有用。所有範例都是用 Kotlin 寫的。我嘗試到處寫註釋,並盡可能讓那些用 Java 編寫的人清楚。這是我們的類別結構:
MainActivity
- 這是主類別。 ListAdapter
- 在這裡我們將清單綁定到視圖元素。 ViewHolder
(其中有幾個)-這是我們對每種類型元素的標記。項目類別是清單 (pojo) 的資料。Xml 檔案是標記(activity_main
用於主螢幕,其餘用於每種類型的清單元素)。我們需要做的第一件事是新增一個依賴項,以便我們可以使用清單(由於某種原因,在建立專案時缺少此依賴項)。build.gradle
在區塊中的文件中,dependencies
我們新增以下行:
implementation "androidx.recyclerview:recyclerview:1.1.0"
接下來我們找到該文件activity_main.xml
:這裡只有我們的列表元素 - RecyclerView
。我們會根據需要新增任何縮排和格式。接下來我們創建類別BaseViewHolder
。它將是抽象的,因為我們所有的類別都將從它繼承ViewHolder
。沒有它也是可能的,但我仍然建議確保所有繼承類別在結構上都相同。(在 Kotlin 中,使用extends
和代替。與 Java 中一樣,這裡您可以繼承一個類別並實作多個介面)接下來,我們建立資料類別:、和。在 Java 中,這些是帶有建構函式和 getter 的普通類別。 implement
:
ItemTitle
ItemGoogle
ItemApple
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
這裡我們重點放在一下介面ListMarker
。
//это наш маркер. Его должны реализовать все айтемы, которые будут
//отображаться в конечном итоге в списке
interface ListMarker
我們所有的資料類別都實作這個介面以將它們轉換為相同的類型。稍後我們的列表中將需要這個。接下來,我們為清單的每個元素建立視圖持有者。不要忘記他們每個人都繼承自BaseViewHolder
. 在這些類別中,我們指定哪個元素view
對應於資料類別中的欄位。
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()
}
}
這裡沒有什麼特定的邏輯,一切都極為簡單。此外,每個視窗都有自己的佈局: list_item_title.xml
list_item_google.xml
list_item_apple.xml
現在讓我們繼續最困難的部分 - 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<*>
當我們從 繼承我們的類別時RecyclerView.Adapter
,我們需要重寫三個方法:onCreateViewHolder
, getItemCount
, onBindViewHolder
。 onCreateViewHolder
- 在這裡我們初始化我們的類別ViewHolder
。 getItemCount
— 此方法負責清單大小。 onBindViewHolder
- 這裡我們將列表元素傳遞給我們的類別ViewHolder
。對於具有一種元素類型的常規列表,這就足夠了。但對於不同的類型,我們仍然需要重新定義方法getItemViewType
(為此,我們使用適配器類別頂部的常數。在 Java 中,您可以使用final
變數)。同樣在 Java 中,您可以使用onBindViewHolder
正規. 最後,讓我們繼續我們的主類 - 。 when
if
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
這是我們的and 初始化的地方ListAdapter
。這是很多人都遇過的漂亮的樣板程式碼。在該方法中,stubData
我用資料填充了列表(如您所見,具有不同元素的資料)並將該列表傳遞給適配器。接下來,我們啟動我們的應用程序,我們應該在螢幕上看到類似這樣的內容:如您所見,一個列表中有不同的元素,這是我們的目標。PS 忘了說Extension
。它看起來是這樣的:
//это расширение для класса 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)
這不是一個類,而是一個文件,這就是為什麼它裡面沒有名稱。但現在我們可以在適配器中簡單地使用它inflate
來代替長結構。不幸的是,這不是為 Java 提供的,所以編寫LayoutInflater.from(context).inflate
. 就這樣。下次見:) GitHub鏈接
GO TO FULL VERSION