孟老板 ListAdapter封装, 告别Adapter代码 (一)

ListAdapter封装- 告别Adapter代码(一) 入门

listAdapter? 是的你没有听错... 但它不是ListView那个Adapter
它是 androidx.recyclerview.widget 包下为 RecycleView 服务的类;

ListAdapter 的优势:

  1. 原Adapter增删改 有各种 notifyItem.. 操作, 而 ListAdapter 一个 submitList 方法即可满足所有操作;
  2. AsyncListDiffer 异步计算新旧数据差异, 并通知 Adapter 刷新数据
  3. 由数据驱动, 无论增删改查 我们只需要关心并操作数据集. 这很 MVVM

推荐文章:

  1. BaseAdapter封装
  2. ListAdapter封装, 告别Adapter代码 (一)
  3. ListAdapter封装, 告别Adapter代码 (二)
  4. ListAdapter封装, 告别Adapter代码 (三)
  5. ListAdapter封装, 告别Adapter代码 (四)
  6. 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

引用官方话术:
DiffUtilListAdapter 能够高效改变元素的奥秘所在。DiffUtil 会比较新旧列表中增加、移动、删除了哪些元素,然后输出更新操作的列表将原列表中的元素高效地转换为新的元素。

简单理解:
ListAdpater 就是通过 DiffUtil 计算前后集合的差异, 得出增删改的结果. 通知Adapter做出对应更新;

3.1 areItemsTheSame():

它比较两个对象是否是同一个 Item;
常见的比较方式: 可自行根据使用场景或个人习惯选用

  1. 比较内存地址: java( ==) kotlin( ===) 不推荐. 在paging中,对象可能被重建
  2. 比较两个对象的 Id; 一般对象在库表中都有主键 ID 参数; 相同的情况下,认定为同一条记录;
  3. equals: java(obj.equals(other)); kotlin(==)

3.2 areContentsTheSame()

在已经确定同一 Item 的情况下, 再确定是否有内容更新;
网上给出的比较方式几乎全是 equals; 但 equals 运用不当根本刷新不了 Item;

  1. 当 areItemsTheSame() 选用 比较内存地址 的方式时, areContentsTheSame() 不能用equals方式;
  2. 当某个具体的 Item 更新时, 必定会替换为一个新实体对象时. 可以用 equals 方式; 也就是说,当我给某个动态条目点赞时, 必须要 copy 一个新的动态对象, 给新对象设置点赞状态为 true; 然后再用新对象替换掉数据集中的旧对象. equals 刷新才能奏效;
  3. 当更新某个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中, 请求成功后直接操作数据集合 更新列表即可.

posted @ 2021-06-09 18:31  孟老板  阅读(1311)  评论(0编辑  收藏  举报