DiffUtil.ItemCallback和ListAdapter的出现,让列表的刷新性能和简单性都得到了提升,但同时还是有些坑
ListAdapter继承自RecyclerView.Adapter,主要是实现了submitList方法来归一化提交数据,这样会触发在子线程对比数据差异,然后再在主线程更新有差异化的数据,这里的差异化可分为:
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时会触发此方法,用于返回之间有差异化的数据
以下先定义一个数据类:
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
这里还有个方法,而且默认是调用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))