Android:使用DiffUtil更新多视图类型的RecyclerView
嘿,你已经找对方向啦!针对多类型RecyclerView的DiffUtil实现,你写的areItemsTheSame逻辑完全没问题,咱们把剩下的部分补全,再聊聊这种场景下的小技巧。
第一步:优化类型安全(用基类代替Object)
为了避免大量instanceof的繁琐判断,先给所有Item类型定义一个基类,统一处理类型标识和唯一ID:
abstract class BaseRecyclerItem { // 返回当前Item的类型标识,比如0代表ViewType1,1代表ViewType2 public abstract int getItemViewType(); // 返回每个Item的唯一标识,用来判断是否是同一个Item public abstract String getUniqueId(); } // 你的第一个视图类型 class ViewType1 extends BaseRecyclerItem { private String uniqueId; private String title; private String content; // 构造方法、getter、setter省略 @Override public int getItemViewType() { return 0; } @Override public String getUniqueId() { return uniqueId; } // 重写equals方法,方便后续内容比较 @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; ViewType1 viewType1 = (ViewType1) o; return Objects.equals(title, viewType1.title) && Objects.equals(content, viewType1.content); } } // 你的第二个视图类型 class ViewType2 extends BaseRecyclerItem { private String uniqueId; private String imageUrl; private int count; // 构造方法、getter、setter省略 @Override public int getItemViewType() { return 1; } @Override public String getUniqueId() { return uniqueId; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; ViewType2 viewType2 = (ViewType2) o; return count == viewType2.count && Objects.equals(imageUrl, viewType2.imageUrl); } }
第二步:完善DiffUtil.Callback
基于基类来写DiffCallback,代码会更简洁且不易出错:
public class TestDiffCallback extends DiffUtil.Callback { private final List<BaseRecyclerItem> oldList; private final List<BaseRecyclerItem> newList; public TestDiffCallback(List<BaseRecyclerItem> oldList, List<BaseRecyclerItem> newList) { this.oldList = oldList; this.newList = newList; } @Override public int getOldListSize() { return oldList.size(); } @Override public int getNewListSize() { return newList.size(); } @Override public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) { BaseRecyclerItem oldItem = oldList.get(oldItemPosition); BaseRecyclerItem newItem = newList.get(newItemPosition); // 先判断类型是否一致,类型不同肯定不是同一个Item if (oldItem.getItemViewType() != newItem.getItemViewType()) { return false; } // 类型一致的话,比较唯一ID return oldItem.getUniqueId().equals(newItem.getUniqueId()); } @Override public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) { BaseRecyclerItem oldItem = oldList.get(oldItemPosition); BaseRecyclerItem newItem = newList.get(newItemPosition); // 直接调用重写的equals方法比较所有内容 return oldItem.equals(newItem); } // 可选:局部更新优化(只刷新变化的字段,提升性能) @Nullable @Override public Object getChangePayload(int oldItemPosition, int newItemPosition) { BaseRecyclerItem oldItem = oldList.get(oldItemPosition); BaseRecyclerItem newItem = newList.get(newItemPosition); Bundle payload = new Bundle(); if (oldItem instanceof ViewType1 && newItem instanceof ViewType1) { ViewType1 oldV1 = (ViewType1) oldItem; ViewType1 newV1 = (ViewType1) newItem; if (!Objects.equals(oldV1.getTitle(), newV1.getTitle())) { payload.putString("title", newV1.getTitle()); } if (!Objects.equals(oldV1.getContent(), newV1.getContent())) { payload.putString("content", newV1.getContent()); } } else if (oldItem instanceof ViewType2 && newItem instanceof ViewType2) { ViewType2 oldV2 = (ViewType2) oldItem; ViewType2 newV2 = (ViewType2) newItem; if (!Objects.equals(oldV2.getImageUrl(), newV2.getImageUrl())) { payload.putString("imageUrl", newV2.getImageUrl()); } if (oldV2.getCount() != newV2.getCount()) { payload.putInt("count", newV2.getCount()); } } // 没有变化的字段就返回null,走全量刷新逻辑 return payload.isEmpty() ? null : payload; } }
第三步:在Adapter中使用DiffUtil更新数据
当你从API拿到新数据后,这样高效更新RecyclerView:
// 假设你的Adapter里有数据源:private List<BaseRecyclerItem> mItems; public void updateData(List<BaseRecyclerItem> newItems) { DiffUtil.Callback callback = new TestDiffCallback(mItems, newItems); DiffUtil.DiffResult result = DiffUtil.calculateDiff(callback); // 替换旧数据 mItems.clear(); mItems.addAll(newItems); // 通知Adapter执行差异化更新 result.dispatchUpdatesTo(this); }
关键注意点
- 唯一ID要靠谱:
getUniqueId()必须返回服务器生成的唯一标识,绝对不能用Item的position(position会随列表变化而改变)。 - 局部更新的妙用:如果你的Item布局复杂,重写
getChangePayload可以避免整个Item刷新,大幅提升流畅度。在Adapter的onBindViewHolder里可以这样处理payload:@Override public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position, @NonNull List<Object> payloads) { if (payloads.isEmpty()) { // 全量刷新,绑定所有数据 onBindViewHolder(holder, position); } else { // 局部更新,只处理变化的字段 Bundle payload = (Bundle) payloads.get(0); if (holder instanceof ViewType1ViewHolder) { ViewType1ViewHolder vh = (ViewType1ViewHolder) holder; if (payload.containsKey("title")) { vh.titleTv.setText(payload.getString("title")); } if (payload.containsKey("content")) { vh.contentTv.setText(payload.getString("content")); } } else if (holder instanceof ViewType2ViewHolder) { ViewType2ViewHolder vh = (ViewType2ViewHolder) holder; if (payload.containsKey("imageUrl")) { // 加载新图片 Glide.with(vh.itemView).load(payload.getString("imageUrl")).into(vh.imageIv); } if (payload.containsKey("count")) { vh.countTv.setText(String.valueOf(payload.getInt("count"))); } } } }
这样你的多类型RecyclerView就能高效完成差异化更新啦!
内容的提问来源于stack exchange,提问作者spoonsearch
相关产品推荐
相关产品推荐

