5.1.0| DiffUtil.ItemCallback实现细颗粒度的差分更新

DiffUtil.ItemCallback和ListAdapter的出现,让列表的刷新性能和简单性都得到了提升,但同时还是有些坑

ListAdapter继承自RecyclerView.Adapter,主要是实现了submitList方法来归一化提交数据,这样会触发在子线程对比数据差异,然后再在主线程更新有差异化的数据,这里的差异化可分为:

  • 更新列表中item变化的部分
  • 更新item内部发生的部分变化

ItemCallback定义

public abstract static class ItemCallback {
    public abstract boolean areItemsTheSame(@NonNull T oldItem, @NonNull T newItem);
    public abstract boolean areContentsTheSame(@NonNull T oldItem, @NonNull T newItem);
    public Object getChangePayload(@NonNull T oldItem, @NonNull T newItem) {
            return null;
        }
}

areItemsTheSame用于判断两个item是否代表同一份信息,比如判断id是否一致:(oldItem.id == newItem.id),如果返回false则会直接去更新ui
areContentsTheSame用于判断要显示的内容是否完全一致,areItemsTheSame返回true会进入此判断
getChangePayload 这是可选实现的,如果areItemsTheSame为true而areContentsTheSame为false时会触发此方法,用于返回之间有差异化的数据

ItemCallback实现

以下先定义一个数据类:

data class SmartNoteItem (
    val id: String,
    var original: String = "",
    var isEdit: Boolean = false,
)

实现ItemCallback类

class SmartNoteItemDiff:DiffUtil.ItemCallback() {

        val keyIsEdit = "keyIsEdit"
        val keyName = "keyName"

        override fun areItemsTheSame(oldItem: SmartNoteItem, newItem: SmartNoteItem): Boolean {
            return (oldItem.id == newItem.id)
        }

        override fun areContentsTheSame(
            oldItem: SmartNoteItem,
            newItem: SmartNoteItem
        ): Boolean {
            return (oldItem==newItem)
        }

        override fun getChangePayload(oldItem: SmartNoteItem, newItem: SmartNoteItem): Any? {
            return Bundle().apply {
                if (oldItem.isEdit != newItem.isEdit) putBoolean(keyIsEdit, newItem.isEdit)
                if (oldItem.original != newItem.original) putString(keyName, newItem.original)
            }
        }

    }

更新数据的要点

mAdapter.submitList(mData)

提交数据通过这样方式,就可触发对比更新差异化的问题,使用非常简单

此时要实现ui的整体更新还需要实现 ListAdapter

public abstract void onBindViewHolder(@NonNull VH holder, int position);

这大家都用过了的,不是重点,那item差分payloads在哪里?先看:

public void onBindViewHolder(@NonNull VH holder, int position,
        @NonNull List payloads) {
    onBindViewHolder(holder, position);
}
 
  

这里还有个方法,而且默认是调用onBindViewHolder(holder, position)的,那接下来看看怎么用payloads更新数据:

override fun onBindViewHolder(
    holder: DataBindingViewHolder,
    position: Int,
    payloads: MutableList
) {
    super.onBindViewHolder(holder, position, payloads)
    if (!payload.isEmpty()) {
    val bundle = payload[0] as Bundle
    L.d(TAG) { "bind: payload got position=$position,bundle=${bundle}" }
    bundle.keySet().forEach {
        if (it == diff.keyIsEdit){
            val isEdit=bundle.getBoolean(diff.keyIsEdit)
            L.d(TAG, "bind:addTextChangedListener position=${position},isEdit=${isEdit}")
            if (isEdit) { //mSmartNoteViewModel.mEditState
                db.tvOriginal.enableEdit()
            }else{
                db.tvOriginal.disableEdit()
            }
        }

        if (it == diff.keyName) {
            db.tvOriginal.setText(bundle.getString(it))
        }

    }
}
}

以上的核心就是通过bundle来对比每个要更新的key。

你也可能会遇到的坑

不能重复提交同一个列表

mData.add(...)
mAdapter.submitList(mData)

像这样重复提交同一个列表引用,是不会触发界面更新的,不信请看AsyncListDiffer中的实现

public void submitList(@Nullable final List 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;
        }
}

当newList == mList时,此时方法会被很尴尬的return掉了,那么下面的方式可行吗?

mAdapter.submitList(mData.toMutableList())

以上创建一个新列表的表达,其它也是没用的,因为列表里面引用的对象是一样的,也就导致ItemCallback返回都是校验成相等的,那怎么做?

val newList=mData.toMutableList()
val first=newList.first().copy()
first.original="第一条数据被更新:${random()}"
newList.removeAt(0)
newList.add(0,first)
mAdapter.submitList(newList)

类似这样copy原item再做数据更新是会生效的,或者

mAdapter.notifyItemChanged(0,diff.getChangePayload(oldItem,first))

你可能感兴趣的:(移动开发,android)