孟老板 ListAdapter封装, 告别Adapter代码 (一)
ListAdapter封装- 告别Adapter代码(一) 入门
listAdapter? 是的你没有听错... 但它不是ListView那个Adapter
它是 androidx.recyclerview.widget 包下为 RecycleView 服务的类;
ListAdapter 的优势:
- 原Adapter增删改 有各种 notifyItem.. 操作, 而 ListAdapter 一个 submitList 方法即可满足所有操作;
- AsyncListDiffer 异步计算新旧数据差异, 并通知 Adapter 刷新数据
- 由数据驱动, 无论增删改查 我们只需要关心并操作数据集. 这很 MVVM
推荐文章:
- BaseAdapter封装
- ListAdapter封装, 告别Adapter代码 (一)
- ListAdapter封装, 告别Adapter代码 (二)
- ListAdapter封装, 告别Adapter代码 (三)
- ListAdapter封装, 告别Adapter代码 (四)
- Paging3 官方分页加载工具
开始使用
1.Adapter
- 我们不再继承 RecycleView.Adapter. 而是继承 androidx.recyclerview.widget.ListAdapter
- 泛型提供数据实体类 和 自定义的 ViewHolder
- onCreateViewHolder() 使用的 MVVM模式;
好的直接上代码:
class TestAdapter : ListAdapter<DynamicTwo, NewViewHolder>(DiffCallback()) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): NewViewHolder {
return NewViewHolder(
DataBindingUtil.inflate(
LayoutInflater.from(parent.context),
R.layout.item_dynamic_img, parent, false
)
)
}
override fun onBindViewHolder(holder: NewViewHolder, position: Int) {
holder.bind(getItem(position))
}
}
2.NewViewHolder
接收一个 ViewDataBinding 对象, bind时, 直接通知绑定数据.
open class NewViewHolder(val binding: ViewDataBinding) : RecyclerView.ViewHolder(binding.root){
fun bind(item: DynamicTwo?) {
item?.hasChanged = false //状态标记方式时, 重置状态
binding.setVariable(BR.item, item)
binding.executePendingBindings()
}
}
3.DiffCallback
引用官方话术:
DiffUtil 是 ListAdapter 能够高效改变元素的奥秘所在。DiffUtil 会比较新旧列表中增加、移动、删除了哪些元素,然后输出更新操作的列表将原列表中的元素高效地转换为新的元素。
简单理解:
ListAdpater 就是通过 DiffUtil 计算前后集合的差异, 得出增删改的结果. 通知Adapter做出对应更新;
3.1 areItemsTheSame():
它比较两个对象是否是同一个 Item;
常见的比较方式: 可自行根据使用场景或个人习惯选用
比较内存地址: java( ==) kotlin( ===)不推荐. 在paging中,对象可能被重建- 比较两个对象的 Id; 一般对象在库表中都有主键 ID 参数; 相同的情况下,认定为同一条记录;
- equals: java(obj.equals(other)); kotlin(==)
3.2 areContentsTheSame()
在已经确定同一 Item 的情况下, 再确定是否有内容更新;
网上给出的比较方式几乎全是 equals; 但 equals 运用不当根本刷新不了 Item;
- 当 areItemsTheSame() 选用 比较内存地址 的方式时, areContentsTheSame() 不能用equals方式;
- 当某个具体的 Item 更新时, 必定会替换为一个新实体对象时. 可以用 equals 方式; 也就是说,当我给某个动态条目点赞时, 必须要 copy 一个新的动态对象, 给新对象设置点赞状态为 true; 然后再用新对象替换掉数据集中的旧对象. equals 刷新才能奏效;
- 当更新某个Item, 不确定是否为新Item对象实体时, 不能用 equals 方式;
总结:
同一个内存地址的对象 equals 有个鸡儿用? 有个鸡儿用??
在paging+Room项目中, 应使用 Equals 方式; 否则建议使用: 状态标记方式
状态标记方式:
实体对象中增加: hasChanged: Boolean 字段; 当对象内容变化时,设置 hasChanged 为true; ViewHolder.bind()时,置为false;
所以 DiffCallback 代码如下:
class DiffCallback : DiffUtil.ItemCallback<DynamicTwo>() {
override fun areItemsTheSame(oldItem: DynamicTwo, newItem: DynamicTwo): Boolean {
return oldItem === newItem
}
override fun areContentsTheSame(oldItem: DynamicTwo, newItem: DynamicTwo): Boolean {
return !oldItem.hasChanged
}
}
4.Activity 使用
Adapter的初始化 跟以往没有区别. 然后就是操作数据 然后 submitList 提交数据
mAdapter = TestAdapter()
mDataBind.rvRecycle.let {
it.layoutManager = LinearLayoutManager(mActivity)
it.adapter = mAdapter
}
fun addData(){
val data = mutableListOf<DynamicTwo>()
//currentList 不需要判空, 它有默认的空集合
data.addAll(mAdapter.currentList)
repeat(10){
data.add(DynamicTwo())
}
mAdapter.submitList(data)
}
fun deleteItem(position: Int){
val data = mutableListOf<DynamicTwo>()
data.addAll(mAdapter.currentList)
data.removeAt(position)
mAdapter.submitList(data)
}
细心小伙伴应该可以看出新增或删除时, 这个 data 是一个新集合对象;
为什么这里必须要用新集合对象操作? 我们来看一下 submitList 的源码:
public void submitList(@Nullable List<T> list) {
mDiffer.submitList(list);
}
//AsyncListDiffer mDiffer
public void submitList(@Nullable final List<T> newList,
@Nullable final Runnable commitCallback) {
// incrementing generation means any currently-running diffs are discarded when they finish
final int runGeneration = ++mMaxScheduledGeneration;
// ** java代码, 判断是否同一内存地址. 这里同一对象时会 return
if (newList == mList) {
// nothing to do (Note - still had to inc generation, since may have ongoing work)
if (commitCallback != null) {
commitCallback.run();
}
return;
}
final List<T> previousList = mReadOnlyList;
// fast simple remove all
if (newList == null) {
//noinspection ConstantConditions
int countRemoved = mList.size();
mList = null;
mReadOnlyList = Collections.emptyList();
// notify last, after list is updated
mUpdateCallback.onRemoved(0, countRemoved);
onCurrentListChanged(previousList, commitCallback);
return;
}
// fast simple first insert
if (mList == null) {
mList = newList;
mReadOnlyList = Collections.unmodifiableList(newList);
// notify last, after list is updated
mUpdateCallback.onInserted(0, newList.size());
onCurrentListChanged(previousList, commitCallback);
return;
}
.....
}
mList 为旧集合对象; 可以看出, 当新旧数据为同一对象时 return, 就不再往下执行了.
ListAdapter 认为新旧数组为同一对象时, nothing to do.
我们可以认为这是 ListAdapter 的一个特性. 也许它只是提醒我们 不要做无效刷新操作;
当然我们也可以重写 submitList 方法, 然后自动新建数据集.
更新操作:
fun updateItem(position: Int){
val data = mutableListOf<DynamicTwo>()
data.addAll(mAdapter.currentList)
data[position].let {
it.title = "变变变 我是百变小魔女"
it.hasChanged = true
}
mAdapter.submitList(data)
}
总结
ListAdapter 可完美的 由数据驱动 UI, 增删改可以放到 ViewModel中, 请求成功后直接操作数据集合 更新列表即可.