Android——RecyclerView入门学习之DiffUt
学习资料:
十分感谢 :)
DiffUtil
是android.support:design:24.2.0
中推出的一个RecyclerView
的辅助工具类
作用:
当Adapter
中的数据发生变化时,用来比较Adpater
中变化前和后两个数据集合,实现差异化更新。而两个集合的整个比较过程,以及针对数据变化该调用哪些notifyData
方法,内部会自动进行处理
1.简单使用 <p>
使用的步骤:
- 创建一个
DiffUtilCallback
继承DiffUtil.Callback
,重写里面的抽象方法 - 使用
DiffUtil.calculateDiff(callback)
,计算差异结果,需要DiffUtilCallback
,返回对象为DiffUtil.DiffResult
。 - 使用
DiffResult
对象,通过diffResult.dispatchUpdatesTo(adapter)
方法,将DiffUtil
与Adapter
关联起来
大致的思路就是这个样子,基本也是死套路
效果:
改变全部学科,并在加入一条点击RecyclerView
的一个item
,将学科全部改变,并在postion
为2
处,加入一个item
1.1 创建DiffUtilCallback
直接继承DiffUtil.Callback
,之后Anddroid Studio
便会自动提示必须要重写4
个的方法
class DiffUtilCallback extends DiffUtil.Callback {
private List<ResourceBean> oldList = new ArrayList<>();
private List<ResourceBean> newList = new ArrayList<>();
public void setNewList(List<ResourceBean> newList) {
if (null != newList) {
this.newList.clear();
this.newList.addAll(newList);
}
}
public void setOldList(List<ResourceBean> oldList) {
if (null != oldList) {
this.oldList.clear();
this.oldList.addAll(oldList);
}
}
/**
* 数据变化之前,旧的数据集合size
*/
@Override
public int getOldListSize() {
Log.e("oldList.size", "---->" + oldList.size());
return oldList.size();
}
/**
* 数据变化之后,新的数据集合size
*/
@Override
public int getNewListSize() {
Log.e("newList.size", "---->" + newList.size());
return newList.size();
}
/**
* 根据position来对新旧集合中的Object进行判断
*/
@Override
public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) {
ResourceBean oldBean = oldList.get(oldItemPosition);
ResourceBean newBean = newList.get(newItemPosition);
Log.e("areItemsTheSame", oldItemPosition + "--" + newItemPosition + "---->" + oldBean.getSubject().equals(newBean.getSubject()));
return oldBean.getSubject().equals(newBean.getSubject());//比较科目
}
/**
* 根据position来对新旧集合中的Object的内容进行判断
*/
@Override
public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) {
ResourceBean oldBean = oldList.get(oldItemPosition);
ResourceBean newBean = newList.get(newItemPosition);
Log.e("areContentsTheSame", oldItemPosition + "--" + newItemPosition + "---->" + oldBean.getName().equals(newBean.getName()));
return oldBean.getName().equals(newBean.getName());//比较学科
}
/**
* 返回的特定的差异化结果
*/
@Nullable
@Override
public Object getChangePayload(int oldItemPosition, int newItemPosition) {
ResourceBean oldBean = oldList.get(oldItemPosition);
ResourceBean newBean = newList.get(newItemPosition);
Bundle diffBundle = new Bundle();
if (!oldBean.getName().equals(newBean.getName())) {
diffBundle.putSerializable("newBean",newBean);
}
return diffBundle;
}
}
ResourceBean
内只有两个String
类型的subject,name
的set,get
方法,不再给出
1.2 Adapter代码
适配器就是一个一般写法的适配,代码比较简单
class PayloadAdapter extends RecyclerView.Adapter<PayloadAdapter.RecyclerHolder> {
private Context mContext;
private List<ResourceBean> dataList = new ArrayList<>();
private onRecyclerItemClickerListener mListener;
public PayloadAdapter(RecyclerView recyclerView) {
this.mContext = recyclerView.getContext();
}
/**
* 增加点击监听
*/
public void setItemListener(onRecyclerItemClickerListener mListener) {
this.mListener = mListener;
}
/**
* 设置数据源
*/
public void setData(List<ResourceBean> dataList) {
if (null != dataList) {
this.dataList.clear();
this.dataList.addAll(dataList);
}
}
public List<ResourceBean> getDataList() {
return dataList;
}
@Override
public RecyclerHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(mContext).inflate(R.layout.id_rv_item_layout, parent, false);
return new RecyclerHolder(view);
}
@Override
public void onBindViewHolder(RecyclerHolder holder, int position) {
ResourceBean resourceBean = dataList.get(position);
holder.textView.setText(resourceBean.getSubject() + "--->" + resourceBean.getName());
holder.textView.setOnClickListener(getOnClickListener(position));
}
@Override
public void onBindViewHolder(RecyclerHolder holder, int position, List<Object> payloads) {
super.onBindViewHolder(holder, position, payloads);
if (payloads.isEmpty()) {
Log.e("isEmpty", "---->" + payloads.isEmpty());
onBindViewHolder(holder, position);
}else {
Log.e("isEmpty", "---->no null");
Bundle bundle = (Bundle) payloads.get(0);
ResourceBean resourceBean = (ResourceBean) bundle.get("newBean");
if (null == resourceBean) return;
// Log.e("change", "---->" + change);
holder.textView.setText(resourceBean.getSubject() + "--->" + resourceBean.getName());
}
}
private View.OnClickListener getOnClickListener(final int position) {
return new View.OnClickListener() {
@Override
public void onClick(View v) {
if (null != mListener && null != v) {
mListener.onRecyclerItemClick(v, dataList.get(position), position);
}
}
};
}
@Override
public int getItemCount() {
return dataList.size();
}
class RecyclerHolder extends RecyclerView.ViewHolder {
TextView textView;
private RecyclerHolder(View itemView) {
super(itemView);
textView = (TextView) itemView.findViewById(R.id.tv__id_item_layout);
}
}
/**
* 点击监听回调接口
*/
public interface onRecyclerItemClickerListener {
void onRecyclerItemClick(View view, Object data, int position);
}
}
加入了一个onBindViewHolder(RecyclerHolder holder, int position, List<Object> payloads)
方法
1.3 与Adapter进行关联 <p>
在创建Adapter的Activity中,完整代码:
class DiffUtil2Activity extends AppCompatActivity {
private RecyclerView rv;
private PayloadAdapter mAdapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_diff_util2);
init();
}
private void init() {
rv = (RecyclerView) findViewById(R.id.rv_diff2_util_activity);
//布局管理器
LinearLayoutManager manager = new LinearLayoutManager(this);
manager.setOrientation(LinearLayoutManager.VERTICAL);
rv.setLayoutManager(manager);
//设置ItemDecoration
rv.addItemDecoration(new DividerItemDecoration(this, LinearLayoutManager.VERTICAL));
//适配器
mAdapter = new PayloadAdapter(rv);
rv.setAdapter(mAdapter);
//添加数据
addData();
}
private void addData() {
DiffUtilCallback callback = new DiffUtilCallback();
callback.setOldList(mAdapter.getDataList());
List<ResourceBean> oldList = new ArrayList<>();
for (int i = 'A'; i < 'D'; i++) {
for (int j = 0; j < 15; j++) {
ResourceBean resourceBean = new ResourceBean();
resourceBean.setSubject("学科" + (char) i);
resourceBean.setName("资料" + j);
oldList.add(resourceBean);
}
}
callback.setNewList(oldList);
DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(callback);//子线程计算差异
diffResult.dispatchUpdatesTo(mAdapter);
//记得将新数据替换adapter中旧的集合数据
mAdapter.setData(oldList);
//设置点击事件
mAdapter.setItemListener(new PayloadAdapter.onRecyclerItemClickerListener() {
@Override
public void onRecyclerItemClick(View view, Object data, int position) {
changeData();
}
});
}
private void changeData() {
DiffUtilCallback callback = new DiffUtilCallback();
callback.setOldList(mAdapter.getDataList());
List<ResourceBean> newList = new ArrayList<>();
for (int i = 'A'; i < 'D'; i++) {
for (int j = 0; j < 15; j++) {
ResourceBean resourceBean = new ResourceBean();
resourceBean.setSubject("学科" + (char) i);
resourceBean.setName("吃饭课" + j);
newList.add(resourceBean);
}
}
ResourceBean r = new ResourceBean();
r.setSubject("体育课");
r.setName("---->666666");
newList.add(4, r);
callback.setNewList(newList);
//这是主线程。下面有案例放在了子线程中
DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(callback);//子线程计算差异
diffResult.dispatchUpdatesTo(mAdapter);
mAdapter.setData(newList);
}
@Override
protected void onDestroy() {
super.onDestroy();
if (null != rv) rv.setAdapter(null);
}
}
而关联操作关键的代码也就2
行
DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(callback);
diffResult.dispatchUpdatesTo(adapter);//与Adapter进行关联
这时计算两个List
的过程是放在UI主线程
。到了实际开发中,数据量往往会比较大,计算差异化这一步,最好做些处理放在一个子线程中
在上一篇RecyclerView.Adapter学习的1.5小节一系列的notifyData方法
中,移除或者增加同类型对象的item
时,说会感觉卡顿,使用DiffUtil
感觉好多了,可能心理作用。。。
如果只是想做个赞
,感觉没必要使用DiffUtil
,RecycelrView.Adapter
提供的局部刷新感觉蛮好用的了。有一个特别适合使用的场景便是下拉刷新
,不仅有动画,效率也有提高,尤其是下拉刷新
操作后,Adapter
内集合数据并没有发生改变,不需要进行重新绘制RecyclerView
时
2. DiffUtil <p>
DiffUtil.DiffResult
和DiffUtil.Callback
是DiffUtil
的两个内部类。DiffUtil
对外提供的public
方法只有2
个
方法1:
/**
* Calculates the list of update operations that can covert one list into the other one.
*
* @param cb The callback that acts as a gateway to the backing list data
*
* @return A DiffResult that contains the information about the edit sequence to convert the old list into the new list.
*/
public static DiffResult calculateDiff(Callback cb) {
return calculateDiff(cb, true);
}
这个方法用于计算newList
和oldList
的差异,在内部计算出后的操作便是将两个列表转换成为另一个新的集合,但转换过程并不用再操心的
在方法内,调用了两个参数的calculateDiff(cb, true)
,便是方法2
方法2:
/**
* Calculates the list of update operations that can covert one list into the other one.
* <p>
* If your old and new lists are sorted by the same constraint and items never move (swap positions), you can disable move detection which takes <code>O(N^2)</code> time where N is the number of added, moved, removed items.
*
* @param cb The callback that acts as a gateway to the backing list data
* @param detectMoves True if DiffUtil should try to detect moved items, false otherwise.
*
* @return A DiffResult that contains the information about the edit sequence to convert the old list into the new list.
*/
public static DiffResult calculateDiff(Callback cb, boolean detectMoves) {
...
//做过修改的Eugene W. Myers’s difference算法
final Snake snake = diffPartial(cb, range.oldListStart, range.oldListEnd, range.newListStart, range.newListEnd, forward, backward, max);
...
return new DiffResult(cb, snakes, forward, backward, detectMoves);
-
boolean detectMoves:是否检测
item
是否移动,使用方法1
默认为ture
,这样相对fasle
也会耗时一些
具体哪种场景下设置为false,目前还不了解,有知道的同学请留言告诉一声
在这个方法中,calculateDiff()
算是整个DiffUtil
的核心操作,计算差异化操作,里面涉及到的算法以后再进行学习
最后返回了一个DiffResult
对象,这个对象内部封装着进行了差异化比较操作后的转换的数据集合。这个对象内部涉及到的转换操作,暂时也不打算进行学习,等对于DiffUtil
的使用相对熟练一些后,再进行学习
2.1 DiffUtil.Callback <p>
在这个抽象类中,一共有5个抽象方法:
方法 | 作用 |
---|---|
public abstract int getOldListSize() |
新集合的size |
public abstract int getNewListSize() |
旧集合的size |
public abstract boolean areItemsTheSame() |
判断两个集合中Object 是否相同 |
public abstract boolean areContentsTheSame() |
检查两个item 的对象内容是否相同 |
public Object getChangePayload() |
@Nullable ,非必需重写的方法,得到封装新旧集合中两个item 负载对象,默认返回null
|
前两个方法很好理解,后面3个方法需要根据源码中的注释来学习
2.1.1 areItemsTheSame()判断Item中Objecct是否相同 <p>
源码:
/**
* Called by the DiffUtil to decide whether two object represent the same Item.
* 进行比较新旧集合中在oldItemPosition与newItemPosition两个对象是否相同
*
* For example, if your items have unique ids, this method should check their id equality.
*例如,当集合中的对象有唯一的标记ids时,就比较两个对象的ids
*
* @param oldItemPosition The position of the item in the old list
* 对象在旧的集合中的position
* @param newItemPosition The position of the item in the new list
* 对象在新的集合中的position
* @return True if the two items represent the same object or false if they are different.
* 返回比较结果
*/
public abstract boolean areItemsTheSame(int oldItemPosition, int newItemPosition);
这个方法的返回结果会对areContentsTheSame()
方法有影响
2.1.2 areContentsTheSame()判断Objecct中的内容是否相同<p>
源码:
/**
* Called by the DiffUtil when it wants to check whether two items have the same data.
* 比较新旧集合对象中的内容是否一样
* DiffUtil uses this information to detect if the contents of an item has changed.
* DiffUtil利用这个方法的返回值来检测一个对象是否发生变化
*
* DiffUtil uses this method to check equality instead of {@link Object#equals(Object)}
* DiffUtil使用这个方法代替equals()来检测是否相等
* so that you can change its behavior depending on your UI.
* 可以根据UI需求来改变返回值
*
* For example, if you are using DiffUtil with a {@link android.support.v7.widget.RecyclerView.Adapter RecyclerView.Adapter}, you should return whether the items' visual representations are the same.
* 例如,在配合RecyclerView.Adapter使用时,需要返回RecycelrView的item视觉效果是否一致
*这里视觉效果应该指的就是动画吧
*
* This method is called only if {@link #areItemsTheSame(int, int)} returns{@code true} for these items.
*这个方法只有在areItemsTheSame()方法返回ture时才会被调用
* @param oldItemPosition The position of the item in the old list
* @param newItemPosition The position of the item in the new list which replaces the oldItem
* @return True if the contents of the items are the same or false if they are different.
*/
public abstract boolean areContentsTheSame(int oldItemPosition, int newItemPosition);
只有当areContentsTheSame()
方法返回true
时,这个方法才会被掉y用。不难理解,只有听一个类型的对象才可以比较的意义
2.1.3 getChangePayload()得到封装差异化信息的对象 <p>
源码:
/**
* When {@link #areItemsTheSame(int, int)} returns {@code true} for two items and {@link #areContentsTheSame(int, int)} returns false for them, DiffUtil calls this method to get a payload about the change.
* 当areItemsTheSame()返回true并且areContentsTheSame返回false,DiffUtil便会调用这个方法将两个item的差异封装在一个负载对象中
*
* <p>
* For example, if you are using DiffUtil with {@link RecyclerView}, you can return the particular field that changed in the item and your{@link android.support.v7.widget.RecyclerView.ItemAnimator ItemAnimator} can use that information to run the correct animation.
* 例如,当配合RecyclerView使用DiffUtil时,可以将新的item改变的特定属性返回,还可以使用返回的差异属性利用RecyclerView.ItemAnimator使用改变的动画
*
* <p>
* Default implementation returns {@code null}.
*默认返回为null
* @param oldItemPosition The position of the item in the old list
* @param newItemPosition The position of the item in the new list
*
* @return A payload object that represents the change between the two items.
*/
@Nullable
public Object getChangePayload(int oldItemPosition, int newItemPosition) {
return null;
}
使用这个方法,有特定的条件:
areItemsTheSame()返回true,areContentsTheSame返回false
这个方法还要重写RecyclerView.Adapter
的onBindViewHolder(RecyclerHolder holder, int position, List<Object> payloads)
DiffUtil
将返回的封装差异化的对象存储在一个List
集合中,集合payloads
对象不会为null
,但可能会为empty
,也就是只有集合对象,而集合中却是空的。在onBindViewHolder()
需要加判断payloads.isEmpty()
,若为empty
就调用两个参数的onBindViewHolder()
简单使用:
在DiffUtilCallback
中对应:
/**
* 返回的特定的差异化结果
*/
@Nullable
@Override
public Object getChangePayload(int oldItemPosition, int newItemPosition) {
ResourceBean oldBean = oldList.get(oldItemPosition);
ResourceBean newBean = newList.get(newItemPosition);
Bundle diffBundle = new Bundle();
if (!oldBean.getName().equals(newBean.getName())) {
diffBundle.putSerializable("newBean",newBean);
}
return diffBundle;
}```
**在Adapter中对应:**
```java
@Override
public void onBindViewHolder(RecyclerHolder holder, int position, List<Object> payloads) {
super.onBindViewHolder(holder, position, payloads);
if (payloads.isEmpty()) {
Log.e("isEmpty", "---->" + payloads.isEmpty());
onBindViewHolder(holder, position);
}else {
Log.e("isEmpty", "---->no null");
Bundle bundle = (Bundle) payloads.get(0);
ResourceBean resourceBean = (ResourceBean) bundle.get("newBean");
if (null == resourceBean) return;
// Log.e("change", "---->" + change);
holder.textView.setText(resourceBean.getSubject() + "--->" + resourceBean.getName());
}
}
注意要先判断payloads
中是否含有差异化数据
子线程模拟网络请求,完整的Activity代码:
public class DiffUtilActivity extends AppCompatActivity {
private RecyclerView rv;
private PayloadAdapter mAdapter;
private StaticHandler mHandler = null;
private static final int ADD_DATA = 2 << 5;
private static final int CHANGE_DATA = 2 << 6;
private List<ResourceBean> oldList;
private List<ResourceBean> newList;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_diff_util);
init();
}
private void init() {
mHandler = new StaticHandler(DiffUtilActivity.this);
rv = (RecyclerView) findViewById(R.id.rv_diff_util_activity);
//布局管理器
LinearLayoutManager manager = new LinearLayoutManager(this);
manager.setOrientation(LinearLayoutManager.VERTICAL);
rv.setLayoutManager(manager);
//设置ItemDecoration
rv.addItemDecoration(new DividerItemDecoration(this, LinearLayoutManager.VERTICAL));
//适配器
mAdapter = new PayloadAdapter(rv);
rv.setAdapter(mAdapter);
//设置点击事件
mAdapter.setItemListener(new PayloadAdapter.onRecyclerItemClickerListener() {
@Override
public void onRecyclerItemClick(View view, Object data, int position) {
changeData();
}
});
//添加数据
addData();
}
public static class StaticHandler extends Handler {
private final WeakReference<DiffUtilActivity> mWeakReference;
private StaticHandler(DiffUtilActivity activity) {
mWeakReference = new WeakReference<>(activity);
}
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
DiffUtilActivity activity = mWeakReference.get();
if (null != activity) {
PayloadAdapter adapter = activity.mAdapter;
if (msg.what == ADD_DATA) {
List<ResourceBean> list = activity.oldList;
DiffUtil.DiffResult diffResult = (DiffUtil.DiffResult) msg.obj;
diffResult.dispatchUpdatesTo(adapter);
adapter.setData(list);
// adapter.notifyDataSetChanged(); //由上面代替
} else if (msg.what == CHANGE_DATA) {
List<ResourceBean> newData = activity.newList;
DiffUtil.DiffResult diffResult = (DiffUtil.DiffResult) msg.obj;
diffResult.dispatchUpdatesTo(adapter);
adapter.setData(newData);
}
}
}
}
/**
* 点击之后,更改数据
*/
private void changeData() {
new Thread(new Runnable() {
@Override
public void run() {
Message message = mHandler.obtainMessage();
DiffUtilCallback callback = new DiffUtilCallback();
callback.setOldList(mAdapter.getDataList());
newList = new ArrayList<>();
//模拟下拉刷新数据没有变化
// for (int i = 'A'; i < 'D'; i++) {
// for (int j = 0; j < 15; j++) {
// ResourceBean resourceBean = new ResourceBean();
// resourceBean.setSubject("学科" + (char) i);
// resourceBean.setName("资料" + j);
// newList.add(resourceBean);
// }
// }
for (int i = 'A'; i < 'D'; i++) {
for (int j = 0; j < 15; j++) {
ResourceBean resourceBean = new ResourceBean();
resourceBean.setSubject("学科" + (char) i);
resourceBean.setName("吃饭课" + j);
newList.add(resourceBean);
}
}
ResourceBean r = new ResourceBean();
r.setSubject("体育课");
r.setName("---->666666");
newList.add(4, r);
callback.setNewList(newList);
DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(callback);//子线程计算差异
message.what = CHANGE_DATA;
message.obj = diffResult;
try {
TimeUnit.MILLISECONDS.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
mHandler.sendMessage(message);
}
}).start();
}
/**
* 第一次加入数据
*/
private void addData() {
new Thread(new Runnable() {
@Override
public void run() {
Message message = mHandler.obtainMessage();
DiffUtilCallback callback = new DiffUtilCallback();
callback.setOldList(mAdapter.getDataList());
oldList = new ArrayList<>();
for (int i = 'A'; i < 'D'; i++) {
for (int j = 0; j < 15; j++) {
ResourceBean resourceBean = new ResourceBean();
resourceBean.setSubject("学科" + (char) i);
resourceBean.setName("资料" + j);
oldList.add(resourceBean);
}
}
try {
TimeUnit.MILLISECONDS.sleep(500);//模拟网络耗时
} catch (InterruptedException e) {
e.printStackTrace();
}
callback.setNewList(oldList);
DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(callback);//子线程计算差异
message.what = ADD_DATA;
message.obj = diffResult;
mHandler.sendMessage(message);
}
}).start();
}
@Override
protected void onDestroy() {
super.onDestroy();
if (null != rv) rv.setAdapter(null);
}
}
补充:
2016 12月8号 00:16 发现之前写的有严重错误,已经修改。改代码一直到1:02,越看越感觉代码烂,困了,先睡觉,以后对DiffUtil
再熟悉一点再来修改
3.最后 <p>
这篇学习记录得稀里糊涂的,希望不会坑到看博客的同学
本人很菜,有错误请指出
共勉 :)