DiffUtil这个控件工具出来也有一年多时间了,以前在项目中使用不是使用深拷贝集合,就是使用Serializable序列化反序列化数据,而后进行新旧数据对比刷新,这样的操做即很麻烦,也很不优雅,而后以前看了谷歌组件化架构中的Paging Library的源码,发现谷歌对于DiffUtil的封装用法挺不错的,因此今天我就个人思路来封装一下DuffUtil。android
首先我先考虑adapter刷新的问题,每次数据更改,就要本身手动去调用adapter的notify方法,想要动画效果的话,还得本身算下刷新的位置,移除或者添加的刷新方法,想到若是直接在数据更新完成后就自动调用adapter的刷新方法就行了,既然这样,那就先从集合数据入手,既然要刷新数据,那就在对应集合的增删改查中添加对应的刷新代码吧,这里我用接口把刷新的需求回调出去,总体代码以下:git
/**
* #PagedList
*/
class PagedList<T> extends ArrayList<T> {
private Callback mCallback;
public void setCallback(Callback callback) {
mCallback = callback;
}
@Override
public boolean add(T t) {
boolean add = super.add(t);
if (add && mCallback != null) {
mCallback.onInserted(size() - 1, 1);
}
return add;
}
@Override
public void add(int index, T element) {
super.add(index, element);
if (mCallback != null) {
mCallback.onInserted(index, 1);
}
}
@Override
public boolean addAll(Collection<? extends T> c) {
boolean b = super.addAll(c);
if (b && mCallback != null) {
mCallback.onInserted(size() - c.size(), c.size());
}
return b;
}
@Override
public boolean addAll(int index, Collection<? extends T> c) {
boolean b = super.addAll(index, c);
if (b && mCallback != null) {
mCallback.onInserted(index, c.size());
}
return b;
}
@Override
public T remove(int index) {
T remove = super.remove(index);
if (remove != null && mCallback != null) {
mCallback.onRemoved(index, 1);
}
return remove;
}
@Override
public boolean removeAll(Collection<?> c) {
boolean b = super.removeAll(c);
if (b && mCallback != null) {
mCallback.onRemoved(size(), c.size());
}
return b;
}
@Override
protected void removeRange(int fromIndex, int toIndex) {
super.removeRange(fromIndex, toIndex);
if (mCallback != null) {
mCallback.onRemoved(fromIndex, toIndex - fromIndex);
}
}
@Override
public boolean remove(Object o) {
boolean remove = super.remove(o);
if (remove && mCallback != null) {
mCallback.onRemoved(indexOf(o), 1);
}
return remove;
}
@Override
public void clear() {
int size = size();
super.clear();
if (mCallback != null) {
mCallback.onRemoved(0, size);
}
}
/**
* 用于刷新数据的回调.
*/
public interface Callback {
/**
* 插入数据.
*/
public void onInserted(int position, int count);
/**
* 移除数据.
*/
@SuppressWarnings("unused")
public void onRemoved(int position, int count);
}
}复制代码
代码很简单,就是在add,move这些会引发数据变化的方法里,插入回调方法的执行。而后重要的就是这个回调的实现了。github
如今开始就有个问题了,由于通常咱们的网络数据通过gson转换后的通常都是ArrayList而不是咱们如今这个PagedList,这个咱们就得想到如何把ArrayList转换为PagedList,并且要作到在开发页面不接触到PagedList,这时候就要提供一个方法转换了,我想的方法是这个:bash
public static <T> List<T> getConvertList(List<T> listData) {
PagedList<T> list = new PagedList<>();
list.addAll(listData);
return list;
}复制代码
这样就能够将ArrayList转为PagedList,并且也不会在开发页面中出现PagedList了,这个方法我就先放到基类的adapter中了,网络
接下来就要考虑让用户去实现的DiffUtil.Callback方法了,不过因为本身强迫症,我仍是不想让开发页面接触到DiffUtil.Callback方法,因此就只能本身模仿写个回调的类了。代码以下架构
public abstract class DiffCallBack<T> {
public abstract boolean areItemsTheSame(T oldItem, T newItem);
public abstract boolean areContentsTheSame(T oldItem, T newItemn);
@Nullable
public Object getChangePayload(T oldItem, T newItem) {
return null;
}
}复制代码
就三个方法,和DiffUtil.Callback方法同样,只不过参数从position变为具体的实体类了。app
如今貌似给开发页面须要的东西已经齐了,那就能够开始构造基类的adapter了。这里想着若是基类adapter中还处理diffUtil的东西,感受耦合太重,因此咱们分出一个类专门处理diffUtil的一系列事件。dom
开始构造处理diff的类,DiiffAdapterHelper:ide
在DiiffAdapterHelper咱们须要实现两个功能,一就是PagedList中的回调方法,咱们要实现出来,二就是不一样的两个集合数据咱们要调用DiffUtil的计算功能,这里也涉及到线程,天然而然可使用线程池了。工具
首先把PagedList的回调实现:
/**
* 设置pagedList的回调.
*/
private void setPagedCallback() {
PagedList list = (PagedList) mListData;
list.setCallback(new PagedList.Callback() {
@Override
public void onInserted(int position, int count) {
mBaseAdapter.notifyItemRangeInserted(position, count);
mBaseAdapter.notifyItemRangeChanged(position, count);
}
@Override
public void onRemoved(int position, int count) {
mBaseAdapter.notifyItemRangeRemoved(position, count);
mBaseAdapter.notifyItemRangeChanged(position, count);
}
});
}复制代码
把对应的集合传进来,这样就能够强转成PagedList进行操做,固然也可能传进来是ArrayList,这个处理待会再作。
而后再建立对应的DiffUtil.Callback,这里我但愿只有一个DiffUtil.Callback对象,并且新旧数据能够本身传,这里再构造一个DiffUtil.Callback:
public abstract class BaseCallBack<T> extends DiffUtil.Callback {
protected List<T> mOldData;
protected List<T> mNewData;
public BaseCallBack() {
}
public BaseCallBack(@NonNull List<T> oldData, @NonNull List<T> newData) {
mOldData = oldData;
mNewData = newData;
}
public void setOldData(List<T> oldData) {
mOldData = oldData;
}
public void setNewData(List<T> newData) {
mNewData = newData;
}
@Override
public int getOldListSize() {
return mOldData.size();
}
@Override
public int getNewListSize() {
return mNewData.size();
}
@Override
public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) {
return areItemsTheSame(mOldData.get(oldItemPosition), mNewData.get(newItemPosition));
}
@Override
public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) {
return areContentsTheSame(mOldData.get(oldItemPosition), mNewData.get(newItemPosition));
}
public abstract boolean areItemsTheSame(T oldItem, T newItem);
public abstract boolean areContentsTheSame(T oldItem, T newItem);
}复制代码
这个原本是要给开发页面去实现抽象方法的,可是总以为要把diffUtil隐藏起来,因此这个就给这里底层用了,而后就能够建立对应的Callback了。
/**
* 建立真正的diffUtilCallback.
*/
private void createNewDiff(final DiffCallBack<T> diffCallback) {
mDiffCallback = new BaseCallBack<T>() {
@Override
public boolean areItemsTheSame(T oldItem, T newItem) {
return diffCallback.areItemsTheSame(oldItem, newItem);
}
@Override
public boolean areContentsTheSame(T oldItem, T newItem) {
return diffCallback.areContentsTheSame(oldItem, newItem);
}
@Nullable
@Override
public Object getChangePayload(int oldItemPosition, int newItemPosition) {
return diffCallback.getChangePayload(mOldData.get(oldItemPosition), mNewData.get(newItemPosition));
}
};
}复制代码
只要拿到用户传的的DiffCallBack,就能够组装出真正的diff对象了,
还有计算数据,那确定得在线程中操做,出于规范,这里用了线程池。还有个handler切换到主线程刷新。
/**
* 线程池(固定2个).
*/
private ExecutorService mExecutorService = Executors.newFixedThreadPool(2);
/**
* 切换主线程用.
*/
private Handler mHandler = new Handler();
mExecutorService.submit(new Runnable() {
@Override
public void run() {
final DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(mDiffCallback, true);
mHandler.post(new Runnable() {
@Override
public void run() {
diffResult.dispatchUpdatesTo(mBaseAdapter);
}
});
}
});复制代码
基本问题所有解决了,再来看完整的:
public class DiffAdapterHelper<T> {
/**
* 线程池(固定2个).
*/
private ExecutorService mExecutorService = Executors.newFixedThreadPool(2);
/**
* diff计算.
*/
private BaseCallBack<T> mDiffCallback;
/**
* 对应的adapter.
*/
private BaseAdapter<T> mBaseAdapter;
/**
* 数据.
*/
private List<T> mListData;
/**
* 切换主线程用.
*/
private Handler mHandler = new Handler();
public DiffAdapterHelper(List<T> listData, BaseAdapter<T> baseAdapter, DiffCallBack<T> diffCallback) {
mListData = listData;
mBaseAdapter = baseAdapter;
createNewDiff(diffCallback);
setPagedCallback();
}
/**
* 设置pagedList的回调.
*/
private void setPagedCallback() {
PagedList list = (PagedList) mListData;
list.setCallback(new PagedList.Callback() {
@Override
public void onInserted(int position, int count) {
mBaseAdapter.notifyItemRangeInserted(position, count);
mBaseAdapter.notifyItemRangeChanged(position, count);
}
@Override
public void onRemoved(int position, int count) {
mBaseAdapter.notifyItemRangeRemoved(position, count);
mBaseAdapter.notifyItemRangeChanged(position, count);
}
});
}
/**
* 建立真正的diffUtilCallback.
*/
private void createNewDiff(final DiffCallBack<T> diffCallback) {
mDiffCallback = new BaseCallBack<T>() {
@Override
public boolean areItemsTheSame(T oldItem, T newItem) {
return diffCallback.areItemsTheSame(oldItem, newItem);
}
@Override
public boolean areContentsTheSame(T oldItem, T newItem) {
return diffCallback.areContentsTheSame(oldItem, newItem);
}
@Nullable
@Override
public Object getChangePayload(int oldItemPosition, int newItemPosition) {
return diffCallback.getChangePayload(mOldData.get(oldItemPosition), mNewData.get(newItemPosition));
}
};
}
/**
* 设置新值.
* <p>
* 经过新旧数据对比计算,进入线程池计算
*
* @param listData 列表数据
*/
void setListData(final List<T> listData) {
List<T> oldData = mListData;
this.mListData = listData;
setPagedCallback();
mDiffCallback.setOldData(oldData);
mDiffCallback.setNewData(mListData);
mExecutorService.submit(new Runnable() {
@Override
public void run() {
final DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(mDiffCallback, true);
mHandler.post(new Runnable() {
@Override
public void run() {
//计算结束才开始赋值并刷新
mBaseAdapter.setRealListData(listData);
diffResult.dispatchUpdatesTo(mBaseAdapter);
}
});
}
});
}复制代码
注意一点,我这里的思路是每次有新的数据,并且以前的数据不须要时候,就直接赋值新的集合,例以下拉刷新那样的场景,这样就不用本身去深拷贝原先集合,从而能够用diffUtil进行计算。
接下俩就是构造基类的adapter了。
public abstract class BaseAdapter<T> extends RecyclerView.Adapter<BaseViewHolder> {
protected Context mContext;
/**
* 集合.
*/
protected List<T> mListData;
/**
* 布局.
*/
protected int layoutId;
/**
* 辅助计算.
*/
protected DiffAdapterHelper<T> mAdapterHelper;
public BaseAdapter(@NonNull List<T> listData, @LayoutRes int layoutId, DiffCallBack<T> callBack) {
this.mListData = listData;
this.layoutId = layoutId;
if (mListData instanceof PagedList) {
mAdapterHelper = new DiffAdapterHelper<>(mListData, this, callBack);
}
}
public static <T> List<T> getConvertList(List<T> listData) {
PagedList<T> list = new PagedList<>();
list.addAll(listData);
return list;
}
public final void setListData(List<T> listData) {
if (mAdapterHelper != null) {
mAdapterHelper.setListData(listData);
} else {
setRealListData(listData);
}
}
protected final void setRealListData(List<T> listData) {
mListData = listData;
}
@Override
public BaseViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
mContext = parent.getContext();
return new BaseViewHolder(LayoutInflater.from(mContext)
.inflate(layoutId, parent, false));
}
@Override
public int getItemCount() {
return mListData.size();
}
}复制代码
这里的adapter就很简单了,只是负责判断是否PagedList,否则就不建立DiffAdapterHelper,其余的好像没啥了
到这里就封装结束了,来开始写页面了。
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.yzl.diff.difftest.MainActivity">
<Button
android:id="@+id/bt_add"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="增长数据"
app:layout_constraintHorizontal_weight="1"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toLeftOf="@+id/bt_sub"/>
<Button
android:id="@+id/bt_sub"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="减小数据"
app:layout_constraintHorizontal_weight="1"
app:layout_constraintLeft_toRightOf="@+id/bt_add"
app:layout_constraintRight_toLeftOf="@+id/bt_change"/>
<Button
android:id="@+id/bt_change"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="切换数据源"
app:layout_constraintHorizontal_weight="1"
app:layout_constraintLeft_toRightOf="@+id/bt_sub"
app:layout_constraintRight_toRightOf="parent"/>
<android.support.v7.widget.RecyclerView
android:id="@+id/rv"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layoutManager="LinearLayoutManager"
app:layout_constraintTop_toBottomOf="@+id/bt_add"/>
</android.support.constraint.ConstraintLayout>
复制代码
public class MainActivity extends AppCompatActivity {
private Button mBtAdd;
private Button mBtSub;
private Button mBtChange;
private RecyclerView mRv;
private BaseAdapter<MockData> mAdapter;
private List<MockData> mMockDataList;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initView();
initList();
initListener();
}
private void initView() {
mBtAdd = (Button) findViewById(R.id.bt_add);
mBtSub = (Button) findViewById(R.id.bt_sub);
mBtChange = (Button) findViewById(R.id.bt_change);
mRv = (RecyclerView) findViewById(R.id.rv);
}
private void initList() {
mMockDataList = new ArrayList<>();
mMockDataList.add(new MockData(1, "一1"));
mMockDataList.add(new MockData(2, "一2"));
mMockDataList.add(new MockData(3, "一3"));
mMockDataList.add(new MockData(4, "一4"));
mMockDataList.add(new MockData(5, "一5"));
mMockDataList.add(new MockData(6, "一6"));
mMockDataList.add(new MockData(7, "一7"));
mMockDataList.add(new MockData(8, "一8"));
mMockDataList.add(new MockData(9, "一9"));
mMockDataList.add(new MockData(10, "一10"));
mMockDataList = BaseAdapter.getConvertList(mMockDataList);
mAdapter = new MainAdapter(mMockDataList, R.layout.item_main, new DiffCallBack<MockData>() {
@Override
public boolean areItemsTheSame(MockData oldItem, MockData newItem) {
return oldItem.getId() == newItem.getId();
}
@Override
public boolean areContentsTheSame(MockData oldItem, MockData newItem) {
return oldItem.getName().equals(newItem.getName());
}
});
mRv.setAdapter(mAdapter);
}
private void initListener() {
mBtAdd.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Random random = new Random();
mMockDataList.add(random.nextInt(mMockDataList.size()), new MockData(mMockDataList.size() + 1, "一2"));
}
});
mBtSub.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mMockDataList.remove(mMockDataList.size() - 1);
}
});
mBtChange.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
List<MockData> data = new ArrayList<>();
data.add(new MockData(1, "一8"));
data.add(new MockData(2, "一2"));
data.add(new MockData(3, "一3"));
data.add(new MockData(8, "一1"));
data.add(new MockData(9, "一9"));
data.add(new MockData(10, "一10"));
mMockDataList = BaseAdapter.getConvertList(data);
mAdapter.setListData(mMockDataList);
}
});
}
}复制代码
运行效果如图
大概效果和代码就是这样了,本文只是简单提供下个人diffUtil的封装思路,但愿会对你有点帮助,若是你理解了这个思路,你能够试着封装一下,可能会想到更好的思路。
顺便安利一下我以前写的 PagingLibrary源码浅析:juejin.im/post/5a08fe…
谷歌的 这个库确实很赞,你能够去看下源码感觉一下。
本文demo地址:github.com/a1048823898…