RecyclerView扩展知识: DiffUtil

1.简介

DiffUtil是support-v7:24.2.0中的新工具类,它用来比较两个数据集,寻找出旧数据集-》新数据集的最小变化量,定向刷新列表。

最大的用处就是在RecyclerView刷新时,不再使用mAdapter.notifyDataSetChanged()全部刷新,全部刷新的缺点:

  1. 不会触发RecyclerView的动画(删除、新增、位移、change动画)
  2. 性能较低,毕竟是刷新了一遍整个RecyclerView , 极端情况下:新老数据集一模一样,效率是最低的

使用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 payloads) {
        if (payloads.isEmpty()) {
            onBindViewHolder(holder, position);
        } else {
            Bundle bundle = (Bundle) payloads.get(0);
            holder.mTvName.setText(bundle.getString("KEY_NAME"));
        }
    }

    @Override
    public void onBindViewHolder(@NonNull DiffUtilAdapter.ViewHolder holder, final int position) {
        TestBean bean = mList.get(position);
        holder.mTvName.setText(bean.getName());
    }
    
    @Override
    public int getItemCount() {
        return mList.size();
    }

    static class ViewHolder extends RecyclerView.ViewHolder {

        TextView mTvName;
        
        ViewHolder(View itemView) {
            super(itemView);
            mTvName = itemView.findViewById(R.id.tv_name);
        }
    }
}

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 payloads) {
        if (payloads.isEmpty()) {
            onBindViewHolder(holder, position);
        } else {
            Bundle bundle = (Bundle) payloads.get(0);
            holder.mTvName.setText(bundle.getString("KEY_NAME"));
        }
    }

    @Override
    public void onBindViewHolder(@NonNull AsyncListDifferAdapter.ViewHolder holder, final int position) {
        TestBean bean = mDiffer.getCurrentList().get(position);
        holder.mTvName.setText(bean.getName());
    }

    @Override
    public int getItemCount() {
        return mDiffer.getCurrentList().size();
    }

    static class ViewHolder extends RecyclerView.ViewHolder {

       ......
    }
}

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就是在这里帮我们做了线程的处理。方便我们正确规范的使用。

你可能感兴趣的:(android,RecyclerView,DiffUtil)