孟老板 BaseAdapter封装(五) ListAdapter

目录:

  ListAdapter 简介及优势

  开始使用

  DiffUtil 

  areContentsTheSame 不能更新条目的原因

 

 

前言:

  listAdapter?? 是的你没有听错…  算了不解释了,都1202年了;  它是    androidx.recyclerview.widget  包下为RecycleView服务的类;

 

ListAdapter 的优势:

  1. 刷新列表只需要 submitList 这一个方法;  而避免原Adapter 增删改 的各种 notifyItem.. 操作; 

  2. AsyncListDiffer 异步计算新旧数据差异, 并通知 Adapter 刷新数据

  3. 真正的数据驱动, 无论增删改查 我们只需要关心并操作数据集. 

 

 使用:

1.新建Adapter 继承  ListAdapter;  泛型提供 数据集实体类 和 自定义的 ViewHolder;  构造函数提供了 自定义的 DiffCallback()

DiffCallback 继承自 DiffUtil.ItemCallback  稍安勿躁后面会讲到;  跳转

class TestAdapter : ListAdapter<DynamicTwo, NewViewHolder>(DiffCallback()) {
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): NewViewHolder {
        TODO("Not yet implemented")
    }

    override fun onBindViewHolder(holder: NewViewHolder, position: Int) {
        TODO("Not yet implemented")
    }
}

View Code

 

2. onCreateViewHolder 的代码比较简单, MVVM模式, 直接返回 用 ViewDataBinding 构造的 ViewHolder; 只有一个可变参数, 即布局资源文件ID;

onBindViewHolder 的代码更简单,  拿到数据实体 并交给 ViewHolder 处理即可;

   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))
    }

 

3. ViewHolder 的代码:  

open class NewViewHolder(val binding: ViewDataBinding) : RecyclerView.ViewHolder(binding.root){
    fun bind(item: BaseItem?) {
        binding.setVariable(BR.item, item)
        binding.executePendingBindings()
    }
}

 

4. 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
}
}

 

5. 操作数据:   Adapter 新建跟以往没有区别. 

mAdapter = TestAdapter()
mDataBind.rvRecycle.let {
it.layoutManager = LinearLayoutManager(mActivity)
it.adapter = mAdapter
}

 

5.1 增删操作比较简单, 只需要更改数据集, 并 submitList 即可

注意:  这里需要用新数据集对象操作!!! 切记

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)
}

fun updateItem(position: Int){
    //TODO 暂放
}

 

为什么这里必须要用新集合对象操作?  我们来看一下 submitList 的源码

  public void submitList(@Nullable List<T> list) {
        mDiffer.submitList(list);
    }

  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;

        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 为旧集合对象; 红字部分(JAVA 代码) 可以看出, 当新旧数据为同一对象时return 就不再往下执行了. 
ListAdapter 认为新旧数组为同一对象时, nothing to do.
我们可以认为这是 ListAdapter 的一个特性. 也许它只是提醒我们 不要做无效刷新操作;
当然我们也可以重写
submitList 方法, 然后自动新建数据集.

5.2 DiffUtil
讲更新操作前, 需要先讲 DiffUtil

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

简单理解:
ListAdpater 就是通过 DiffUtil 计算前后集合的差异, 得出增删改的结果. 通知Adapter做出对应操作;
5.2.1 areItemsTheSame(): 比较两个对象是否是同一个 Item;
常见的比较方式: 可自行根据情况或个人习惯选用
  1.比较内存地址: java(==) kotlin(===)
  2.比较两个对象的 Id; 一般对象在库表中都有主键 ID 参数; 相同的情况下,必定为同一条记录;
  3.equals: java(obj.
equals(other)) kotlin(==)
5.2.2 areContentsTheSame(): 在已经确定同一 Item 的情况下, 再确定是否有内容更新;
网上给出的比较方式几乎全是
equals; equals 运用不当根本刷新不了 Item;
  1.当 areItemsTheSame() 选用 比较内存地址 的方式时, areContentsTheSame() 不能用equals方式;
  2.当某个具体的 Item 更新时, 必定会替换为一个新实体对象时. 可以用 equals 方式;
    也就是说,当我给某个动态条目点赞时, 必须要 copy 一个新的动态对象, 给新对象设置点赞状态为 true; 然后再用新对象替换掉数据集中的旧对象.
equals 刷新才能奏效;
  3.当更新某个Item, 不确定是否为新Item对象实体时, 不能用
equals 方式;

  总结: 同一个内存地址的对象
equals 有个鸡儿用? 有个鸡儿用??

  状态标记方式:
  实体对象中增加: hasChanged: Boolean 字段; 当对象内容变化时,设置 hasChanged 为true; ViewHolder.bind()时,置为false;
 
给最终的 ViewHolder  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
        }
    }

open class NewViewHolder(val binding: ViewDataBinding) : RecyclerView.ViewHolder(binding.root){
    fun bind(item: BaseItem?, index: Int) {
        item?.hasChanged = false
        binding.setVariable(BR.item, item)
        binding.executePendingBindings()
    }
}

 

5.3 更新操作: 

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中, 请求成功后直接操作数据集合 更新列表即可.  

回到顶部