Android RecyclerView 局部刷新
使用常规方式刷新数据
1 局部刷新之 Item 刷新
- Item 整体的刷新
//修改指定 position 上 Item 的刷新
mAdapter.notifyItemChanged(int position)
//添加指定 position 上 Item 的刷新
mAdapter.notifyItemInserted(int position)
//移除指定 position 上 Item 的刷新
mAdapter.notifyItemRemoved(int position)
//修改从 positionStart 开始到 itemCount 数量的 Item 的刷新
mAdapter.notifyItemRangeChanged(int positionStart, int itemCount)
//添加从 positionStart 开始到 itemCount 数量的 Item 的刷新
mAdapter.notifyItemRangeInserted(int positionStart, int itemCount)
//移除从 positionStart 开始到 itemCount 数量的 Item 的刷新
mAdapter.notifyItemRangeRemoved(int positionStart, int itemCount)
//移到指定 fromPosition 到 toPosition 上 Item 的刷新
mAdapter.notifyItemMoved(int fromPosition, int toPosition)
-
PS: 点击一个 Item 的删除按钮进行数据删除 notifyItemRemoved ,如果采用事件监听时会传 position 给点击事件接口方法,此时不能直接写 position,因为它其实变成了 final 类型 (注意如果是 java 代码,在 AS 里会显示下划线),这样就造成 pos 错乱了,需要改用 holder.getBindingAdapterPosition (其实这里 holder 变成 final 了) ,这样才能保证点下一个删除按钮时候的 pos 是正确的,notifyItemInserted 也是类似
-
另外还有一种解决方案,就是删除或新增后用 mAdapter.notifyItemRangeChanged(pos, mAdapter.getData().size() - pos) 强行处理一波,及时更新 pos ,让 pos 能够保持正确
2 常规-局部刷新之 View 刷新
- Item 的部分 View 控件的刷新
通过带 payload 参数的刷新方法
- 自行写逻辑操作 View 实现刷新
//修改指定 position 上 Item 局部 View 的刷新
mAdapter.notifyItemChanged(int position,Object payload)
//修改从 positionStart 开始到 itemCount 数量的 Item 局部 View 的刷新
mAdapter.notifyItemRangeChanged(int positionStart, int itemCount,Object payload)
- 假设 payload 只传 【key 标记】的话,那么在执行刷新前就需要保证数据源先完成更新,然后这样 Adapter 才直接用数据源实现刷新
- 另外 payload 也可以传 【key-value 键值对】,比如 Map,那么 Adapter 可以不用数据源而改用 value 进行刷新
修改 DataItemRVAdapter
- 覆写带 Payload 参数的 onBindViewHolder 方法
@Override
public void onBindViewHolder(@NonNull DataItemViewHolder holder, int position, @NonNull List<Object> payloads) {
if (payloads.isEmpty()) {
//【列表刷新】 或者 【局部刷新之 Item 刷新】
super.onBindViewHolder(holder, position, payloads);
} else {
//不为空【局部刷新之 View 刷新】
Object payload = payloads.get(0);
if (payload instanceof Map) {
//【key-value 键值对】
Map<String, String> map = (Map<String, String>) payload;
for (Map.Entry<String, String> stringObjectEntry : map.entrySet()) {
String key = stringObjectEntry.getKey();
String value = stringObjectEntry.getValue();
//简单封装
bindViewHolder(holder, position, key, value);
}
} else if (payload instanceof String) {
//【key 标记】
String key = (String) payload;
//简单封装
bindViewHolder(holder, position, key, null);
}
}
}
private void bindViewHolder(DataItemViewHolder holder, int position, String key, String value) {
DataItem dataItem = mDataItemList.get(position);
//自行写逻辑操作 View 实现刷新
if (key.equals(KEY_TITLE)) {
holder.tv_title.setText(value != null ? value : dataItem.title);
} else if (key.equals(KEY_SUBTITLE)) {
holder.tv_subtitle.setText(value != null ? value : dataItem.content);
} else if (key.equals(KEY_CONTENT)) {
holder.tv_content.setText(value != null ? value : dataItem.content);
} else if (key.equals(KEY_IMAGE)) {
holder.iv.setImageResource(value != null ? Integer.parseInt(value) : dataItem.imageResId);
}
}
使用 DiffUtil 方式刷新数据
- 通过 DiffUtil 计算得出新旧数据集的最小变化量
- 最终内部还是会调用 Adapter#notifyItemXXX 方法去实现刷新的,所以也能够保留 Item 的动画
- DiffUtil#calculateDiff 推荐在子线程执行
1 DiffUtil-局部刷新之 Item 刷新
- Item 整体的刷新
创建 DiffUtilCallback 继承 DiffUtil.Callback
public class DiffUtilCallback extends DiffUtil.Callback {
private List<DataItem> mOldDataItemList;
private List<DataItem> mNewDataItemList;
public DiffCallback(List<DataItem> oldDataItemList, List<DataItem> newDataItemList) {
this.mOldDataItemList = oldDataItemList;
this.mNewDataItemList = newDataItemList;
}
@Override
public int getOldListSize() {
return mOldDataItemList != null ? mOldDataItemList.size() : 0;
}
@Override
public int getNewListSize() {
return mNewDataItemList != null ? mNewDataItemList.size() : 0;
}
@Override
public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) {
//判断是不是同一个 item 的逻辑
DataItem oldDataItem = mOldDataItemList.get(oldItemPosition);
DataItem newDataItem = mNewDataItemList.get(newItemPosition);
return Objects.equals(oldDataItem.id, newDataItem.id);
}
@Override
public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) {
//areItemsTheSame 返回 true 的情况下会走这个方法,进一步判断这个 item 的内容是否有变化
DataItem oldDataItem = mOldDataItemList.get(oldItemPosition);
DataItem newDataItem = mNewDataItemList.get(newItemPosition);
// id
if (!Objects.equals(oldDataItem.id, newDataItem.id)) {
return false;
}
// name
if (!Objects.equals(oldDataItem.name, newDataItem.name)) {
return false;
}
// content
if (!Objects.equals(oldDataItem.content, newDataItem.content)) {
return false;
}
//如果还有,继续写其他判断条件
//默认返回 true 代表内容相等
return true;
}
}
使用
- 通过 calculateDiff 配合 dispatchUpdatesTo
private void refreshDataByDiffUtil() {
List<DataItem> oldDataItemList = new ArrayList<>(mDataItemRVAdapter.getDataItemList());
//mDataItemList 存放最新的数据
//1 修改数据
//别这么写!!!引用传递 引用传递会导致 calculateDiff 失效
// mDataItemList.get(1).name = "222 new";
DataItem oldDataItem = mDataItemList.get(2);
DataItem newDataItem = new DataItem();
newDataItem.id = oldDataItem.id;
newDataItem.name = "333 new";//就改一个字段
newDataItem.content = oldDataItem.content;
mDataItemList.set(2, newDataItem);
//2 先更新 Adapter 数据源
mDataItemRVAdapter.setDataItemList(mDataItemList);
//3 然后通过 DiffUtil 的方式通知刷新,推荐在子线程中计算
DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(new DiffCallback(oldDataItemList, mDataItemList));
//内部会按不同情况自动调用 Adapter#notifyItemXXX 方法去实现刷新
diffResult.dispatchUpdatesTo(mDataItemRVAdapter);
}
2 DiffUtil-局部刷新之 View 刷新
- Item 的部分 View 控件的刷新
- 同常规方式一样 Adapter 需要覆写带 Payload 参数的 onBindViewHolder 方法
修改 DiffUtilCallback 类
- 继续覆写 getChangePayload 方法
@Nullable
@Override
public Object getChangePayload(int oldItemPosition, int newItemPosition) {
DataItem oldDataItem = mOldDataItemList.get(oldItemPosition);
DataItem newDataItem = mNewDataItemList.get(newItemPosition);
// areItemsTheSame 返回 true 并且 areContentsTheSame 返回 false 的情况才会进来
Map<String, String> payloadMap = new HashMap<>();
// title
if (!Objects.equals(oldDataItem.title, newDataItem.title)) {
payloadMap.put(DataItemRVAdapter.KEY_TITLE, newDataItem.title);
}
// title
if (!Objects.equals(oldDataItem.subtitle, newDataItem.subtitle)) {
payloadMap.put(DataItemRVAdapter.KEY_SUBTITLE, newDataItem.subtitle);
}
// content
if (!Objects.equals(oldDataItem.content, newDataItem.content)) {
payloadMap.put(DataItemRVAdapter.KEY_CONTENT, newDataItem.content);
}
// imageResId
if (oldDataItem.imageResId != newDataItem.imageResId) {
payloadMap.put(DataItemRVAdapter.KEY_IMAGE, String.valueOf(newDataItem.imageResId));
}
if (!payloadMap.isEmpty()) {
return payloadMap;
}
//默认无变化,内部传的是 null
return super.getChangePayload(oldItemPosition, newItemPosition);
}
使用
- 同上