RecyclerView与声明式(上)

313 阅读3分钟

这是我参与11月更文挑战的第2天,活动详情查看:2021最后一次更文挑战

我相信每个 Android 开发者都是用过 RecyclerView(本文中用RV代指) 的,但你用得是否足够优雅呢?

每个实践过 Compose 或者 DataBinding ,甚至是前端的 Vue 和 React 的同学们,你们一定能感觉到声明式UI的优雅。这篇文章我想通过我们经常使用的 RV,用命令式和声明式不同思路的代码,让你用一个新颖的视角来看一个熟悉的事物,希望能帮助你理解一下声明式这个概念。

命令式RV

我打算选择一个经典的 TODO 场景来展开。首先我们定义一个数据类todo,有id(标识)、content(内容)、done(完成状态)这三个属性。

data class Todo(var id:int,var content:String,var done = false)

UI 如下,代码很简单就不贴出来了。

image.png

再写一个RV的Adapter,前期工作就完成了。

MyAdapter.kt (代码适量删减)

class MyAdapter(var list: MutableList<Todo>) : RecyclerView.Adapter<ViewHolder>() {
    override fun onBindViewHolder(viewHolder: ViewHolder, position: Int) {
        val data = list[position]
        // 内容赋值
        // 状态赋值
    }
}

这样我们在Activity中就能显示待办列表了。然后我们有一些需求,需要更改某个item的状态,增删一个item。

命令式的思路一般会这样写:为MyAdapter增加一些操作函数,用来增删或者更改状态。那么代码如下:

MyAdapter.kt (代码适量删减)

class MyAdapter(var list):Adapter(){
    fun updateItemDone(index:Int){
        list[index].done = true
        notifyItemChanged(index)
    }
    fun updateItemTodo(index:Int){
        list[index].done = false
        notifyItemChanged(index)
    }
    fun addItem(data:Todo){
        list.add(data)
        notifyItemInserted(list.size - 1)
    }
    fun deleteItem(index:Int){
        list.remove(index)
        notifyItemRemoved(index)
    }
}

功能写好,然后在几个点击事件回调中调用对应的函数,这样需求就实现了,所有的事情都显得那么岁月静好~

那我们再来看看声明式的思路。

声明式RV

MyAdapter.kt (代码适量删减)

class MyAdapter() : ListAdapter<Todo, ViewHolder>(diffCallback) { {
    override fun onBindViewHolder(viewHolder: ViewHolder, position: Int) {
        val data = getItem(position)
        // 内容赋值
        // 状态赋值
    }
}
                                                                 
object diffCallback : DiffUtil.ItemCallback<Todo>() {
        override fun areItemsTheSame(oldItem: Todo, newItem: Todo): Boolean
            = oldItem.id == newItem.id
​
        override fun areContentsTheSame(oldItem: Todo, newItem: Todo): Boolean 
            = oldItem.content == newItem.content
              && oldItem.done == newItem.done
}

调用处参考下面的代码。

MainActivity.kt (代码适量删减)

class MainActivity(){
    fun initView(){
        //...
        setAdapterData(myAdapter,list)
        rv.setAdapter(myAdapter)
    }
    
    fun setAdapterData(adapter:ListAdapter,list:List<Todo>){
        adapter.submitList(list)
    }
}
data class Todo(val id:int,val content:String,val done = false)

只需要上面这些代码,所有的待办操作就只需要一个setAdapterData函数就能实现,这样是不是很优雅? 当然代码中,会有很多让你感到奇怪的东西,这些我们一一道来。

俩种代码最大的不同,应该就是使用了ListAdpater这个类,因为它你的所有操作只需要对原数据修改之后,再通过setAdapterData函数把新数据传过来就行了,不管你做的是删除还是新增,修改还是移动。那ListAdapter是如何做到的呢?且看这个DiffUtil.ItemCallback,它是用来比较俩个数据差异的类,结果由它计算出来,传给这个ListAdapter,ListAdapter中可以根据差异来对RV的item进行增删修改移动等操作,而且附带动画。

这样RV的操作是不是简单多了,可它跟声明式有什么关系呢?具体细节将在下篇文章中解析,敬请期待:)