关于RecyclerView的更新
RecyclerView在显现静态的列表的数据的时候,咱们用普通的Adapter,然后添加列表,调用notifyDataSetChanged()即可展现列表,可是关于动态改变的列表来说,全靠notifyDataSetChanged()来完结列表更新显得十分没有效率,因为有时候开发者仅仅想增删一个Item,而这却要支付改写悉数列表的代价。于是谷歌又给咱们供给了多种api让咱们完结部分Item的增删查改,如下:
- notifyItemRemoved()
- notifyItemInserted()
- notifyItemRangeChanged()
- …
这些api当然好用可是关于某些场景来说咱们难以下手,例如后台回来的列表的悉数数据,在获取新的列表之后,开发者或许想比较新旧列表的不同,然后更新发生改变的item,这又怎么完结呢?
关于DiffUtil
谷歌依据开发者需求比较新旧列表异同的痛点,推出了DiffUtil东西,它的中心算法是Myers差分算法,有爱好能够自行学习,这篇文章不作深入探讨(其实笔者也不会)。
关于ListAdapter
注:这个ListAdapter是需求额外引进的,给RecyclerView运用的一个Adapter,并非SDK里边的那个,因而需求区别开来。
ListAdapter是谷歌基于上述的DiffUtil进行封装的一个Adapter,简略地继承重写即可达到DiffUtil的作用,高效完结RecyclerView的更新,这个也是本篇的要点。
实战
布局和对应的实体类
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<TextView
android:id="@+id/tv_name"
tools:text="名字"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<TextView
android:id="@+id/tv_age"
tools:text="18岁"
app:layout_constraintStart_toEndOf="@id/tv_name"
app:layout_constraintTop_toTopOf="parent"
android:layout_marginStart="20dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<TextView
android:id="@+id/tv_tall"
tools:text="180cm"
app:layout_constraintStart_toEndOf="@id/tv_age"
app:layout_constraintTop_toTopOf="parent"
android:layout_marginStart="20dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<TextView
android:id="@+id/tv_long"
tools:text="18cm"
app:layout_constraintStart_toEndOf="@id/tv_tall"
app:layout_constraintTop_toTopOf="parent"
android:layout_marginStart="20dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</androidx.constraintlayout.widget.ConstraintLayout>
data class ItemTestBean(
val name:String,
val age:Int,
val tall:Int,
val long:Int
)
重写ListAdapter
ListAdapter的重写包括的关键点比较多,这儿分步骤阐明:
第一步:完结DiffUtil.ItemCallback
这是整个ListAdapter中最最最关键的一个步骤,因为它是DiffUtil知道怎么正确修正列表的中心,咱们直接看代码。
object ItemTestCallback : DiffUtil.ItemCallback<ItemTestBean>() {
override fun areItemsTheSame(oldItem: ItemTestBean, newItem: ItemTestBean): Boolean {
return oldItem.name == newItem.name
}
override fun areContentsTheSame(oldItem: ItemTestBean, newItem: ItemTestBean): Boolean {
return oldItem.name == newItem.name
&& oldItem.age == newItem.age
&& oldItem.tall == newItem.tall
&& oldItem.long == newItem.long
}
}
乍一看十分杂乱,实践原理十分简略,areItemsTheSame()办法判别的是实体类的主键,areContentsTheSame()办法判别的是实体类中会导致UI改变的字段。
第二步:完结viewHolder
这一步和其他的Adapter没什么区别,笔者用了viewBinding,你也能够依据自己项目实践情况改造。
inner class ItemTestViewHolder(private val binding: ItemTestBinding):RecyclerView.ViewHolder(binding.root){
fun bind(bean:ItemTestBean){
binding.run {
tvName.text=bean.name
tvAge.text=bean.age.toString()
tvTall.text=bean.tall.toString()
tvLong.text=bean.long.toString()
}
}
}
第三步:组合成完整的ListAdapter
在ListAdapter中填入相应的泛型(实体类和ViewHolder类型),然后在结构函数中传入咱们方才完结的DiffUtil.ItemCallback即可,完结的两个办法和其他Adapter迥然不同,仅有需求注意的是ListAdapter为咱们供给了一个getItem的快捷办法,因而在onBindViewHolder()时能够直接调用。
class ItemTestListAdapter : ListAdapter<ItemTestBean,ItemTestListAdapter.ItemTestViewHolder>(ItemTestCallback) {
inner class ItemTestViewHolder(private val binding: ItemTestBinding):RecyclerView.ViewHolder(binding.root){
//...省掉
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ItemTestViewHolder {
return ItemTestViewHolder(ItemTestBinding.inflate(LayoutInflater.from(parent.context),parent,false))
}
override fun onBindViewHolder(holder: ItemTestViewHolder, position: Int) {
//通过ListAdapter内部完结的getItem办法找到对应的Bean
holder.bind(getItem(position))
}
}
运用ListAdapter完结列表的增删查改
为了方便演示,运用如下的List和初始化代码:
private val testList = listOf<ItemTestBean>(
ItemTestBean("小明",18,180,18),
ItemTestBean("小红",19,180,18),
ItemTestBean("小东",20,180,18),
ItemTestBean("小刘",18,180,18),
ItemTestBean("小德",15,180,18),
ItemTestBean("小豪",14,180,18),
ItemTestBean("小江",12,180,18),
)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val binding=ActivityMainBinding.inflate(LayoutInflater.from(this))
setContentView(binding.root)
val adapter=ItemTestListAdapter()
binding.rv.adapter=adapter
}
刺进元素
刺进全新的列表
adapter.submitList(testList)
完事了??
是的,咱们只需求调用submitList办法告知Adatper咱们要刺进一个新的列表即可。
部分刺进元素
或许刺进全新列表并不能让你感觉到ListAdapter的精妙之处,因为这和本来的Adapter不同并不大,咱们再来试试往列表中刺进部分的元素,例如咱们要在小刘和小德之间刺进一个新的Item。
咱们对列表转成可变列表(为什么运用不可变列表,原因后面会解释),然后刺进元素,最终调用submitList把新的列表传入进去即可。
val newList=testList.toMutableList().apply {
add(3,ItemTestBean("坤坤鸡",21,150,4))
}
adapter.submitList(newList)
列表更新了,由此可见,无论是添加一个元素仍是多个元素,咱们都只需求调submitList即可。
这儿说一下为什么要从头传入一个新的List而不是对本来的List进行修正,因为源码中有这样一段,笔者估测是因为这个校验差分的逻辑是异步的,假如外部对原列表进行修正会导致内部的逻辑异常(未验证仅仅猜想)。
因而咱们切记要传入新的List而不是对原List进行修正。
public void submitList(@Nullable final List<T> newList,
@Nullable final Runnable commitCallback) {
//...省掉
//校验列表是否是同一个目标
if (newList == mList) {
// nothing to do (Note - still had to inc generation, since may have ongoing work)
if (commitCallback != null) {
commitCallback.run();
}
return;
}
//...省掉
}
删去元素和修正元素
聪明的读者估量也现已猜到了,无论是添加删去和修正,咱们都只需求submitList即可,因为List中就现已包括了列表的更新信息,全部的更新ListAdapter现已主动替咱们完结了。
val newList=testList.toMutableList().apply {
//删去
removeAt(2)
//修正
this[3]=this[3].copy(name = "改名后的小帅哥")
}
adapter.submitList(newList)
列表清空
全部尽在submitList,假如咱们要让列表清空,那咱们就submit一个空目标就行了,十分简略!
adapter.submitList(null)
运用新的列表进行更新(项目中最常见的杂乱场景)
val newList=listOf(
//修正
ItemTestBean("小明",18,20,18),
ItemTestBean("小红",19,180,18),
//刺进
ItemTestBean("蔡徐鸡",20,180,18),
ItemTestBean("小刘",18,180,18),
ItemTestBean("我爱你",14,180,18),
ItemTestBean("小江",12,180,18),
)
adapter.submitList(newList)
咱们能够看到,新的列表相对原列表而言,发生了修正、删去、刺进等操作,假如这些由开发者自己来维护,是十分麻烦的,可是依靠ListAdapter内置的差异性算法,主动帮咱们完结了这些作业。
总结
笔者运用一个简略的案例演示了ListAdapter怎么协助开发者完结列表差异性更新的逻辑,十分合适那些回来整段列表然后更新部分元素的逻辑,例如后台回来的一整段列表,这些列表或许只有一两个元素发生了改变,假如依照传统的notifyDataSetChange()会严峻糟蹋性能,而ListAdapter只会更新那些发生了改变的区域。
假如你的项目不能直接运用ListAdapter,也期望运用这个差分算法,你能够直接运用DiffUtil去更新你项目的Adapter,关于这个DiffUtil的直接运用,网上有许多教程,用起来也并不难,这儿不在赘述。
喜欢请点赞重视,你的支撑是笔者更新的动力。