GAdapter是一个为RecyclerView设计的简单通用的Adapter,它通过指令式的方式配置ViewHolder和绑定数据,使ViewHolder的可复用性大大提高。
GAdapter是对RecyclerView.Adapter、RecyclerView.ViewHolder、DiffUtil等组件的封装,主要针对以下方面:
- 将
ViewHolder与RecyclerView.Adapter解耦,提高ViewHolder的可复用性; - 利用
DiffUtil的diff算法,计算最小改动路径,避免使用notifyDataSetChanged()方法重绘整个RecyclerView,也避免使用notifyItemInserted()等方法带来的额外人工计算成本和风险; - 重写
RecyclerView.Adapter中三个参数的onBindViewHolder()方法,通过payloads的方式实现局部刷新; - 借助
Kotlin语言的函数式特性,使代码更简洁、更优雅;
buildscript {
// ... Your codes
repositories {
// ... Your codes
maven { url 'https://jitpack.io' }
}
}dependencies {
// ... Your codes
implementation "com.github.itgungnir:gadapter:$adapter_version"
}GAdapter框架中使用了Delegate和RecyclableItem来分别对视图和数据进行了代理。
Delegate是对视图的代理,负责RecyclerView中每个item的渲染和数据的绑定。Delegate接口中包含五个方法:
layoutId():配置这个item的视图文件ID,如R.id.xxx;onCreateViewHolder(parent):代理了RecyclerView.Adapter的onCreateViewHolder()方法,负责创建item的视图;onBindViewHolder(holder, data, payloads):代理了RecyclerView.Adapter的两个参数和三个参数的onBindViewHolder()方法,负责向item视图中的各个控件中注入数据;onResume(holder, data):当列表项滑入屏幕时回调的方法;onPause(holder, data):当列表项滑出屏幕时回调的方法;
本框架中默认实现了BaseDelegate类,提供了简单的onCreateViewHolder()方法的实现,并具体化出了onRender(holder, data, payloads)方法用于绑定数据。
RecyclableItem是对数据的代理,负责存储RecyclerView中每个item中数据的存储与比较。RecyclableItem接口中包含三个方法:
isItemSameTo(oldItem):代理了DiffUtil.ItemCallback接口的areItemsTheSame(oldItem, newItem)方法,用来比较两条数据是否是同一条,在这个方法中通常通过比较数据的id等可以唯一标识这条数据的字段进行比较,当判定这两条数据相同时,则底层不会再调用notifyItemXXX()方法进行item的增删;isContentSameTo(oldItem):代理了DiffUtil.ItemCallback接口的areContentsTheSame(oldItem, newItem)方法,用来判断是否需要局部刷新。只有当areItemsTheSame(oldItem, newItem)方法返回true时,才会走这个方法,在这个方法中通常通过比较一些细节性的字段,如是否被选中、倒计时等,如果这些决定局部刷新的字段全都相同,则判定为两条数据完全相同;getChangePayload(oldItem):代理了DiffUtil.ItemCallback接口的getChangePayload(oldItem, newItem)方法,用来生成数据更新的负载payload。只有当areItemsTheSame(oldItem, newItem)方法返回true、areContentsTheSame(oldItem, newItem)方法返回false时,才会走这个方法。在这个方法中将新数据相比于旧数据的变更之处存储到一个Bundle对象中,并在Delegate的onBindViewHolder(holder, data, payloads)方法中回调。
BindMap是RecyclableItem和Delegate之间的桥梁,其中包含三个属性:
type:即item在RecyclerView.Adapter中的viewType;isViewForType:是一个(RecyclableItem) -> Boolean的Lambda表达式,判断当前的RecyclableItem是否符合第三个参数的Delegate的条件;delegate:Delegate对象,即代理该条数据的视图对象。
创建一个实体类实现RecyclableItem接口,不需要实现任何方法:
/**
* 不需要刷新的数据可以直接实现RecyclableItem接口而不需要实现任何方法
*/
data class SimpleListItem(val content: String) : RecyclableItem创建一个BaseDelegate的子类:
class SimpleDelegate : BaseDelegate<SimpleListItem>() {
override fun layoutId(): Int = R.layout.item_simple
override fun onRender(holder: VH, data: SimpleListItem, payloads: MutableList<Bundle?>) {
holder.itemView.tv_content.apply {
text = data.content
setOnClickListener {
Toast.makeText(context, data.content, Toast.LENGTH_SHORT).show()
}
}
}
}初始化GAdapter并绑定数据:
// 通过RecyclerView的扩展函数getGAdapter()来获取一个GAdapter的实例
val listAdapter = list.getGAdapter()
// 通过addDelegate()方法为RecyclerView添加一个viewType,
// 第一个参数是一个Lambda表达式,用于提供RecyclableItem的筛选条件;
// 第二个参数是一个Delegate对象,
// 即符合第一个参数中的筛选条件的RecyclableItem就使用第二个参数中的Delegate进行渲染
.addDelegate({ it is SimpleListItem }, SimpleDelegate())
// 前面的代码都是对GAdapter的配置,最后需要将这个GAdapter绑定到RecyclerView上
.initialize()
val dataList = mutableListOf<RecyclableItem>()
for (i in 1..30) {
dataList.add(SimpleListItem("List item $i"))
}
// 通过refresh()方法刷新列表
listAdapter.refresh(dataList)由于需要刷新或局部刷新,因此创建一个实体类并继承自RecyclableItem接口,实现其中的三个方法来判定什么时候需要刷新:
/**
* 需要局部刷新的数据需要实现RecyclableItem接口并实现其三个方法
*
* DiffUtil的工作机制:
* 存储着两个列表,分别是旧数据列表和新数据列表。当有新列表被赋值到DiffUtil上时,
* 会通过排列组合的方式两两比较数据,具体的规则如下:
* 1. 当areItemsTheSame()方法返回true时,表示两条数据是同一条数据,此时会进入下一步;
* 2. 当areContentsTheSame()方法返回false时,表示两条数据虽然是同一条数据,但其细节内容
* 并不相同,此时会进入下一步;
* 3. getChangePayload()方法会返回新老数据之间的变化的payload,通过这个payload可以局部刷新
*/
data class PayloadsListItem(val content: String, var selected: Boolean = false) : RecyclableItem {
/**
* 判断新老数据是否是同一条数据
*/
override fun isItemSameTo(oldItem: RecyclableItem): Boolean =
this.content == (oldItem as PayloadsListItem).content
/**
* 如果新老数据是同一条数据,则判断是否需要局部刷新
*/
override fun isContentSameTo(oldItem: RecyclableItem): Boolean =
this.selected == (oldItem as PayloadsListItem).selected
/**
* 如果需要局部刷新,则生成局部刷新的payload
*/
override fun getChangePayload(oldItem: RecyclableItem): Bundle? = if (isContentSameTo(oldItem)) {
null
} else {
Bundle().apply { putBoolean("PL_SELECT", selected) }
}
}在Delegate中,需要根据payloads来实现局部刷新,即判断payloads是否为空,如为空则正常加载数据,否则根据payloads中的数据局部刷新页面:
class PayloadsDelegate(private val callback1: (Int) -> Unit) : BaseDelegate<PayloadsListItem>() {
override fun layoutId(): Int = R.layout.item_payloads
override fun onRender(holder: VH, data: PayloadsListItem, payloads: MutableList<Bundle?>) {
holder.itemView.apply {
// 如果payloads不为空,则从中取出新的局部数据,并更新局部控件,以此达到局部更新的目的,减少UI重绘,提升性能
if (payloads.isNotEmpty()) {
payloads[0]?.let { payload ->
payload.keySet().forEach { key ->
when (key) {
"PL_SELECT" -> iv_checker.setImageResource(getCheckerRes(payload.getBoolean(key)))
}
}
}
} else {
// 如果payloads为空,则说明这是一条新数据,直接渲染UI即可
iv_checker.apply {
setImageResource(getCheckerRes(data.selected))
setOnClickListener {
if (holder.adapterPosition == -1) {
return@setOnClickListener
}
callback1.invoke(holder.adapterPosition)
}
}
tv_content.apply {
text = data.content
setOnClickListener {
if (holder.adapterPosition == -1) {
return@setOnClickListener
}
Toast.makeText(context, "点击:${data.content}", Toast.LENGTH_SHORT).show()
}
}
}
}
}
private fun getCheckerRes(selected: Boolean) = when (selected) {
true -> R.drawable.svg_selected_selected
else -> R.drawable.svg_selected_normal
}
}GAdapter对象的创建代码如下:
listAdapter = list.getGAdapter()
// PayloadsDelegate中需要传入一个Lambda,即一个回调,在点击前面的复选框时反选这个复选框
.addDelegate({ it is PayloadsListItem }, PayloadsDelegate { index ->
val targetItem = dataList[index] as PayloadsListItem
dataList[index] = targetItem.copy(selected = !targetItem.selected)
listAdapter.refresh(dataList)
})
.initialize()当一个RecyclerView中有多种viewType时,只需要调用多次addDelegate()方法即可:
val listAdapter = list.getGAdapter()
.addDelegate({ it is MultipleSearchBar }, MultipleSearchBarDelegate())
.addDelegate({ it is MultipleBanner }, MultipleBannerDelegate())
.addDelegate({ it is MultipleHotBar }, MultipleHotBarDelegate())
.addDelegate({ it is MultipleData }, MultipleDataDelegate())
.initialize()Copyright 2019 ITGungnir
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.