1.简介
DiffUtil是support-v7:24.2.0中的新工具类,它用来比较两个数据集,寻找出旧数据集-》新数据集的最小变化量,定向刷新列表。
它最大的用处就是在RecyclerView刷新时,不再使用mAdapter.notifyDataSetChanged()全部刷新,全部刷新的缺点:
使用DiffUtil后,改为如下代码:
DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(new DiffCallBack(mDatas, newDatas), true);
diffResult.dispatchUpdatesTo(mAdapter);
它会自动计算新老数据集的差异,并根据差异情况,自动调用以下四个方法:
adapter.notifyItemRangeInserted(position, count);
adapter.notifyItemRangeRemoved(position, count);
adapter.notifyItemMoved(fromPosition, toPosition);
adapter.notifyItemRangeChanged(position, count, payload);
2. 例子
Bean:
public class TestBean {
private int id;
private String name;
public TestBean(int id, String name) {
this.id = id;
this.name = name;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
DiffUtil.Callback: 用来比较数据集的差异
public class MyDiffUtilCallback extends DiffUtil.Callback{
private List mOldItems;
private List mNewItems;
public void setItems(@NonNull final List oldItems, @NonNull final List newItems) {
mOldItems = oldItems;
mNewItems = newItems;
}
@Override
public int getOldListSize() {
return mOldItems == null ? 0 : mOldItems.size();
}
@Override
public int getNewListSize() {
return mNewItems == null ? 0 : mNewItems.size();
}
/**
* 是否是同一个对象
*/
@Override
public boolean areItemsTheSame(final int oldItemPosition, final int newItemPosition) {
if (mOldItems.get(oldItemPosition) == null || mNewItems.get(newItemPosition) == null){
return false;
}
return mOldItems.get(oldItemPosition).getId() == mNewItems.get(newItemPosition).getId();
}
/**
* 是否是相同内容
*/
@Override
public boolean areContentsTheSame(final int oldItemPosition, final int newItemPosition) {
return mOldItems.get(oldItemPosition).getName().equals(mNewItems.get(newItemPosition).getName());
}
/**
* areItemsTheSame()返回true而areContentsTheSame()返回false时调用,也就是说两个对象代表的数据是一条,但是内容更新了。
*/
@Nullable
@Override
public Object getChangePayload(final int oldItemPosition, final int newItemPosition) {
TestBean oldBean = mOldItems.get(oldItemPosition);
TestBean newBean = mNewItems.get(newItemPosition);
//这里就不用比较核心字段了,一定相等
Bundle payload = new Bundle();
if (!oldBean.getName().equals(newBean.getName())) {
payload.putString("KEY_NAME", newBean.getName());
}
if (payload.size() == 0){
//如果没有变化 就传空
return null;
}
return payload;
}
}
DiffUtilAdapter:
public class DiffUtilAdapter extends RecyclerView.Adapter {
private List mList = new ArrayList();
private LayoutInflater mInflater;
private MyDiffUtilCallback mDiffCallback;
public DiffUtilAdapter(Context mContext) {
mDiffCallback = new MyDiffUtilCallback();
mInflater = LayoutInflater.from(mContext);
}
public void setData(TestBean mData){
mList.add(mData);
notifyItemRangeInserted(getItemCount(), 1);
}
public void setData(List mData){
mDiffCallback.setItems(mList, mData);
DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(mDiffCallback);
diffResult.dispatchUpdatesTo(this);
mList.clear();
mList.addAll(mData);
}
public void removeData(int index){
mList.remove(index);
notifyItemRemoved(index);
if (index != mList.size()) {
notifyItemRangeChanged(index, mList.size() - index);
}
}
public void clear(){
mList.clear();
notifyDataSetChanged();
}
@Override
@NonNull
public DiffUtilAdapter.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
return new ViewHolder(mInflater.inflate(R.layout.item_test, parent, false));
}
@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position, @NonNull List
Activity:
public class DiffUtilActivity extends AppCompatActivity {
private DiffUtilAdapter mDiffUtilAdapter;
private int count = 10;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_sorted_list);
RecyclerView mRecyclerView = findViewById(R.id.rv);
mRecyclerView.setLayoutManager(new LinearLayoutManager(this));
mDiffUtilAdapter = new DiffUtilAdapter(this);
mRecyclerView.setAdapter(mDiffUtilAdapter);
initData();
}
private void addData() {
mDiffUtilAdapter.setData(new TestBean(count, "Item " + count));
count ++;
}
private List mList = new ArrayList();
private void initData() {
mList.clear();
for (int i = 0; i < 10; i++){
mList.add(new TestBean(i, "Item " + i));
}
mDiffUtilAdapter.setData(mList);
}
private void updateData() {
mList.clear();
for (int i = 9; i >= 0; i--){
mList.add(new TestBean(i, "Item " + i));
}
mDiffUtilAdapter.setData(mList);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.menu, menu);
return true;
}
private Random mRandom = new Random();
@Override
public boolean onOptionsItemSelected(MenuItem item) {
int i = item.getItemId();
if (i == R.id.menu_add) {
addData();
} else if (i == R.id.menu_update) {
updateData();
} else if (i == R.id.menu_delete) {
if (mDiffUtilAdapter.getItemCount() > 0){
mDiffUtilAdapter.removeData(mRandom.nextInt(mDiffUtilAdapter.getItemCount()));
}
}else if (i == R.id.menu_clear){
mDiffUtilAdapter.clear();
}
return true;
}
}
3.AsyncListDiffer
不过DiffUtil的问题在于计算数据差异DiffUtil.calculateDiff(mDiffCallback)时是一个耗时操作,需要我们放到子线程去处理,最后在主线程刷新。为了方便这一操作,在support-v7:27.1.0又新增了一个DiffUtil的封装类,那就是AsyncListDiffer。
3.1 首先实现DiffUtil.ItemCallback
public class MyDiffUtilItemCallback extends DiffUtil.ItemCallback {
/**
* 是否是同一个对象
*/
@Override
public boolean areItemsTheSame(@NonNull TestBean oldItem, @NonNull TestBean newItem) {
return oldItem.getId() == newItem.getId();
}
/**
* 是否是相同内容
*/
@Override
public boolean areContentsTheSame(@NonNull TestBean oldItem, @NonNull TestBean newItem) {
return oldItem.getName().equals(newItem.getName());
}
/**
* areItemsTheSame()返回true而areContentsTheSame()返回false时调用,也就是说两个对象代表的数据是一条,但是内容更新了。此方法为定向刷新使用,可选。
*/
@Nullable
@Override
public Object getChangePayload(@NonNull TestBean oldItem, @NonNull TestBean newItem) {
Bundle payload = new Bundle();
if (!oldItem.getName().equals(newItem.getName())) {
payload.putString("KEY_NAME", newItem.getName());
}
if (payload.size() == 0){
//如果没有变化 就传空
return null;
}
return payload;
}
}
3.2 实现实现RecyclerView.Adapter
public class AsyncListDifferAdapter extends RecyclerView.Adapter {
private LayoutInflater mInflater;
// 数据的操作由AsyncListDiffer实现
private AsyncListDiffer mDiffer;
public AsyncListDifferAdapter(Context mContext) {
// 初始化AsyncListDiffe
mDiffer = new AsyncListDiffer<>(this, new MyDiffUtilItemCallback());
mInflater = LayoutInflater.from(mContext);
}
public void setData(TestBean mData){
List mList = new ArrayList<>();
mList.addAll(mDiffer.getCurrentList());
mList.add(mData);
mDiffer.submitList(mList);
}
public void setData(List mData){
// 由于DiffUtil是对比新旧数据,所以需要创建新的集合来存放新数据。
// 实际情况下,每次都是重新获取的新数据,所以无需这步。
List mList = new ArrayList<>();
mList.addAll(mData);
mDiffer.submitList(mList);
}
public void removeData(int index){
List mList = new ArrayList<>();
mList.addAll(mDiffer.getCurrentList());
mList.remove(index);
mDiffer.submitList(mList);
}
public void clear(){
mDiffer.submitList(null);
}
@Override
@NonNull
public AsyncListDifferAdapter.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
return new ViewHolder(mInflater.inflate(R.layout.item_test, parent, false));
}
@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position, @NonNull List
Activity:
public class AsyncListDifferActivity extends AppCompatActivity {
private AsyncListDifferAdapter mAsyncListDifferAdapter;
private int count = 10;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_sorted_list);
RecyclerView mRecyclerView = findViewById(R.id.rv);
mRecyclerView.setLayoutManager(new LinearLayoutManager(this));
mAsyncListDifferAdapter = new AsyncListDifferAdapter(this);
mRecyclerView.setAdapter(mAsyncListDifferAdapter);
initData();
}
private void addData() {
mAsyncListDifferAdapter.setData(new TestBean(count, "Item " + count));
count ++;
}
private List mList = new ArrayList();
private void initData() {
mList.clear();
for (int i = 0; i < 10; i++){
mList.add(new TestBean(i, "Item " + i));
}
mAsyncListDifferAdapter.setData(mList);
}
private void updateData() {
mList.clear();
for (int i = 9; i >= 0; i--){
mList.add(new TestBean(i, "Item " + i));
}
mAsyncListDifferAdapter.setData(mList);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.menu, menu);
return true;
}
private Random mRandom = new Random();
@Override
public boolean onOptionsItemSelected(MenuItem item) {
int i = item.getItemId();
if (i == R.id.menu_add) {
addData();
} else if (i == R.id.menu_update) {
updateData();
} else if (i == R.id.menu_delete) {
if (mAsyncListDifferAdapter.getItemCount() > 0){
mAsyncListDifferAdapter.removeData(mRandom.nextInt(mAsyncListDifferAdapter.getItemCount()));
}
}else if (i == R.id.menu_clear){
mAsyncListDifferAdapter.clear();
}
return true;
}
}
我们简单的看一下AsyncListDiffer 的 submitList源码:
public void submitList(@Nullable final List newList) {
final int runGeneration = ++this.mMaxScheduledGeneration;
if (newList != this.mList) {
if (newList == null) {
// 新数据为null时清空列表
int countRemoved = this.mList.size();
this.mList = null;
this.mReadOnlyList = Collections.emptyList();
this.mUpdateCallback.onRemoved(0, countRemoved);
} else if (this.mList == null) {
// 旧数据为null时添加数据
this.mList = newList;
this.mReadOnlyList = Collections.unmodifiableList(newList);
this.mUpdateCallback.onInserted(0, newList.size());
} else {
final List oldList = this.mList;
// 计算数据差异放在子线程
this.mConfig.getBackgroundThreadExecutor().execute(new Runnable() {
public void run() {
final DiffResult result = DiffUtil.calculateDiff(new Callback() {
...
});
// 主线程刷新列表
AsyncListDiffer.this.mMainThreadExecutor.execute(new Runnable() {
public void run() {
if (AsyncListDiffer.this.mMaxScheduledGeneration == runGeneration) {
AsyncListDiffer.this.latchList(newList, result);
}
}
});
}
});
}
}
}
void latchList(@NonNull List newList, @NonNull DiffResult diffResult) {
this.mList = newList;
this.mReadOnlyList = Collections.unmodifiableList(newList);
// 熟悉的dispatchUpdatesTo方法
diffResult.dispatchUpdatesTo(this.mUpdateCallback);
}
AsyncListDiffer就是在这里帮我们做了线程的处理。方便我们正确规范的使用。