JavaRush /Java Blog /Random-TW /複雜的列表變得簡單
Paul Soia
等級 26
Kiyv

複雜的列表變得簡單

在 Random-TW 群組發布
大家好。我決定提出 Android 中複雜清單的主題。這個話題一開始讓我很困惑。這對我來說似乎很難,但實際上一切都更簡單。我認為現在面臨這個問題的任何人都會發現這篇文章很有用。所有範例都是用 Kotlin 寫的。我嘗試到處寫註釋,並盡可能讓那些用 Java 編寫的人清楚。這是我們的類別結構: 複雜的列表變得簡單 - 1MainActivity- 這是主類別。 ListAdapter- 在這裡我們將清單綁定到視圖元素。 ViewHolder(其中有幾個)-這是我們對每種類型元素的標記。項目類別是清單 (pojo) 的資料。Xml 檔案是標記(activity_main用於主螢幕,其餘用於每種類型的清單元素)。我們需要做的第一件事是新增一個依賴項,以便我們可以使用清單(由於某種原因,在建立專案時缺少此依賴項)。build.gradle在區塊中的文件中,dependencies我們新增以下行:
implementation "androidx.recyclerview:recyclerview:1.1.0"
接下來我們找到該文件activity_main.xml複雜的清單變得簡單 - 2這裡只有我們的列表元素 - RecyclerView。我們會根據需要新增任何縮排和格式。接下來我們創建類別BaseViewHolder。它將是抽象的,因為我們所有的類別都將從它繼承ViewHolder。沒有它也是可能的,但我仍然建議確保所有繼承類別在結構上都相同。(在 Kotlin 中,使用extends和代替。與 Java 中一樣,這裡您可以繼承一個類別並實作多個介面)接下來,我們建立資料類別:、和。在 Java 中,這些是帶有建構函式和 getter 的普通類別。 implement:ItemTitleItemGoogleItemApple
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複雜的列表變得簡單 - 3list_item_google.xml複雜的清單變得簡單 - 4list_item_apple.xml複雜的清單變得簡單 - 5現在讓我們繼續最困難的部分 - 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, onBindViewHolderonCreateViewHolder- 在這裡我們初始化我們的類別ViewHoldergetItemCount— 此方法負責清單大小。 onBindViewHolder- 這裡我們將列表元素傳遞給我們的類別ViewHolder。對於具有一種元素類型的常規列表,這就足夠了。但對於不同的類型,我們仍然需要重新定義方法getItemViewType(為此,我們使用適配器類別頂部的常數。在 Java 中,您可以使用final變數)。同樣在 Java 中,您可以使用onBindViewHolder正規. 最後,讓我們繼續我們的主類 - 。 whenifMainActivity
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我用資料填充了列表(如您所見,具有不同元素的資料)並將該列表傳遞給適配器。接下來,我們啟動我們的應用程序,我們應該在螢幕上看到類似這樣的內容:複雜的清單變得簡單 - 6如您所見,一個列表中有不同的元素,這是我們的目標。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鏈接
留言
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION