JavaRush /Java 博客 /Random-ZH /复杂的列表变得简单
Paul Soia
第 26 级
Kiyv

复杂的列表变得简单

已在 Random-ZH 群组中发布
大家好。我决定提出 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