学习MultiViewAdapter——3
来源:互联网 发布:手机淘宝如何看评价 编辑:程序博客网 时间:2024/06/16 00:06
本文讲到的关于MultiViewAdapter,是学习国外牛人在GitHub上的开源项目。具体的使用方法可以看Wiki。
我们先了解一下DiffUtil这个类是V7里面提供,很多人应该用过,因为这个库用到了这个类,这里先说明一下。
DiffUtil
是一个实用工具类,可以计算两个列表之间的差异,并输出一个更新操作列表,将第一个列表转换为第二个列表,用来更新RecyclerView的适配器
1 . DiffUtil.DiffResult calculateDiff (DiffUtil.Callback cb, boolean detectMoves)
返回计算的结果,第一个参数是DiffUtil.Callback对象, 第二个参数代表是否检测Item的移动,改为false算法效率更高。我们一般定为true。
官网给出的解释:DiffUtil使用Eugene W. Myers的差异算法来计算出一个列表转换为另一个列表的最小更新次数。Myers的算法是不会移动项目的,所以要想检测移动项目,DiffUti需要对结果运行第二遍。如果列表很大,这个操作可能需要很长的时间,所以建议在后台线程上运行这个操作,使用DiffUtil.DiffResult,然后把得到的结果应用到主线程的RecyclerView上。此算法针对空间进行了优化,并使用O(N)空间来查找两个列表之间的最小加法和去除操作数。它具有O(N + D ^ 2)的时间性能,其中D是编辑脚本的长度。如果启用移动检测,则需要额外的O(N ^ 2)时间,其中N是添加和移除项目的总数。如果您的列表已经按照相同的约束(例如:创建的帖子列表是根据时间戳)来排序的,那么可以禁用移动检测以提高性能。
DiffUtil.Callback
DiffUtil在计算两个列表之间的差异时使用的回调类。
2 . areContentsTheSame(int , int )
由DiffUtil调用 当它想要检查两个项目是否具有相同的数据时。DiffUtil使用此信息来检查项目的内容是否已更改。例如:如果你用RecyclerView.Adapter配合DiffUtil使用,你需要返回Item的视觉表现是否相同。当方法areItemsTheSame(int, int)
返回true时才会调用此方法。
3 .areItemsTheSame(int, int)
由DiffUtil调用以决定两个对象是否表示相同的Item。
例如:如果你的Item有唯一的id字段,这个方法就判断id是否相等。
4 . getChangePayload(int ,int)
当areItemsTheSame(int, int)
返回true,而且areContentsTheSame(int, int)
为返回false时, DiffUtil调用此方法来获取关于更改的Payload。
5 .getNewListSize
返回新列表的大小。
6 .getOldListSize
返回旧列表的大小。
DiffUtil.DiffResult
7 .void dispatchUpdatesTo (ListUpdateCallback updateCallback)
将更新操作分发给给定的回调,可以实现ListUpdateCallback
接口,具体的用法后面会讲到。
8 . void dispatchUpdatesTo (Adapter adapter)
官方给出了怎么用:
List oldList = mAdapter.getData(); DiffResult result = DiffUtil.calculateDiff(new MyCallback(oldList, newList)); mAdapter.setData(newList); result.dispatchUpdatesTo(mAdapter);
这样就可以把结果分发给RecyclerView了,那么这个结果怎么用呢?
RecyclerView绑定数据的地方,多了一个参数。以前是这样的onBindViewHolder(ItemViewHolder holder, int adapterPosition)
现在又多了这样一个方法onBindViewHolder(ItemViewHolder holder, int adapterPosition,List<Object> payloads)
所以简单来说就是,如果payloads 为空,那么就执行两个参数的方法,不为空就执行三个参数的方法。
关于DiffUtil的API就说到这里,我们继续最后一块内容DataManager
BaseDataManager
这个方法我们从名字就已经知道这是个数据的基类,主要处理了基本数据的添加,选择事件等,具体的可以去源码里面看
public abstract class BaseDataManager<M> implements ListUpdateCallback { //ListUpdateCallback可以接收列表的更新操作的接口,这个类可以和DiffUtil一起使用来检测两个列表之间的变化。 ...省略一些代码 //数据的集合 private List<M> dataList = new ArrayList<>(); //通过传入RecyclerAdapter,让CoreRecyclerAdapter去实现数据的更新 BaseDataManager(@NonNull RecyclerAdapter adapter) { this.adapter = adapter; } @Override public final void onRemoved(int position, int count) { adapter.notifyBinderItemRangeRemoved(this, position, count); } @Override public final void onMoved(int fromPosition, int toPosition) { adapter.notifyBinderItemMoved(this, fromPosition, toPosition); } @Override public final void onChanged(int position, int count, Object payload) { adapter.notifyBinderItemRangeChanged(this, position, count, payload); } ...省略一些代码 //也是获得集合里面的数量,只不过与下面那个访问权限不同 public final int getCount() { return size(); } public final M get(int index) { return dataList.get(index); } //获取集合里面的数量 int size() { return dataList.size(); } ...省略一些代码}
DataListUpdateManager
class DataListUpdateManager<M> extends BaseDataManager<M> { final PayloadProvider<M> payloadProvider; //构造方法中必须传入RecyclerAdapter,但是可以选择是否调用DiffUtil DataListUpdateManager(@NonNull RecyclerAdapter adapter) { this(adapter, new PayloadProvider<M>() { @Override public boolean areContentsTheSame(M oldItem, M newItem) { return oldItem.equals(newItem); } @Override public Object getChangePayload(M oldItem, M newItem) { return null; } }); } DataListUpdateManager(@NonNull RecyclerAdapter adapter, @NonNull PayloadProvider<M> payloadProvider) { super(adapter); this.payloadProvider = payloadProvider; } final boolean add(M item, boolean notifyDataSetChanged) { ...省略代码 return result; } final boolean addAll(@NonNull Collection<? extends M> items, boolean notifyDataSetChanged) { return addAll(getDataList().size(), items, notifyDataSetChanged); } ...省略代码 //更新的操作,可以更新指定的位置,并且可以使用DiffUtil优雅刷新数据 final void set(int index, M item, boolean notifyDataSetChanged) { M oldItem = getDataList().get(index); getDataList().set(index, item); if (notifyDataSetChanged) { Object isChange= payloadProvider.getChangePayload(oldItem, item); Log.e("bind","set = "+isChange.toString()); onChanged(index, 1, isChange); } } //更新整个集合,这里的notifyDataSetChanged默认是true final void set(List<M> dataList, boolean notifyDataSetChanged) { DiffUtil.DiffResult result = null; if (notifyDataSetChanged) { //使用DiffUtil计算最小更新距离,旧集合对比新的集合 result = DiffUtil.calculateDiff(new DiffUtilCallback<M>(this.getDataList(), dataList) { @Override public boolean areContentsTheSame(M oldItem, M newItem) { return payloadProvider.areContentsTheSame(oldItem, newItem); } @Override public Object getChangePayload(M oldItem, M newItem) { return payloadProvider.getChangePayload(oldItem, newItem); } }); } //将数据设置给新的集合 setDataList(dataList); if (notifyDataSetChanged) { //这里将会通知到RecyclerView的bindView result.dispatchUpdatesTo(this); } } ...省略一些代码}
我们可以看到DataListUpdateManager
继承了BaseDataManager
这样可以获取到集合,然后对添加,删除,更新数据进行了处理,而且调用DiffUtilCallback
与PayloadProvider
将数据优雅的刷新。我们看到了上面写的关于DiffUtil
的讲解,对这个刷新的方式应该也了解了一些,那么我们正好来看看,在这个库里面是怎样使用的。
PayloadProvider
public interface PayloadProvider<M> { boolean areContentsTheSame(M oldItem, M newItem); @SuppressWarnings("UnusedParameters") Object getChangePayload(M oldItem, M newItem);}
里面的方法与DiffUtil.Callback
中的方法基本一样。这个接口的作用是:在使用DiffUtil计算两个list差异时需要用到这个接口。
DiffUtilCallback
public abstract class DiffUtilCallback<M> extends DiffUtil.Callback { private final List<M> oldList; private final List<M> newList; protected DiffUtilCallback(List<M> oldList, List<M> newList) { this.oldList = oldList; this.newList = newList; } @Override public int getOldListSize() { return oldList.size(); } @Override public int getNewListSize() { return newList.size(); } @Override public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) { return oldList.get(oldItemPosition).equals(newList.get(newItemPosition)); } @Override public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) { return areContentsTheSame(oldList.get(oldItemPosition), newList.get(newItemPosition)); } @Nullable @Override public Object getChangePayload(int oldItemPosition, int newItemPosition) { Object payload = getChangePayload(oldList.get(oldItemPosition), newList.get(newItemPosition)); return payload != null ? payload : super.getChangePayload(oldItemPosition, newItemPosition); } public abstract boolean areContentsTheSame(M oldItem, M newItem); public abstract Object getChangePayload(M oldItem, M newItem);}
也很简单,就像API里面说的一样,在areItemsTheSame
判断position是否一致,areContentsTheSame
判断内容是不是一样。getChangePayload
里面看看是不是空的。然后在DataListUpdateManager
通过调用PayloadProvider
来处理数据更新的问题。
DataListManager
...省略一些代码public final class DataListManager<M> extends DataListUpdateManager<M> {public DataListManager(@NonNull RecyclerAdapter adapter) { super(adapter); } public DataListManager(@NonNull RecyclerAdapter adapter, @NonNull PayloadProvider<M> payloadProvider) { super(adapter, payloadProvider); }public final boolean add(M item) { return add(item, true); }public final void set(int index, M item) { set(index, item, true); }}...省略一些代码
为了客户端方便调用,又在DataListUpdateManager
的基础上封装了一层DataListManager
来实现对List数据的操作。
库的作者除了封装了List的Manager,而且还封装了一个DataGroupManager
,用于添加头,脚布局,以及折叠布局的使用。关于DataGroupManager
我们下次再说吧,内容比较多。
我们从上面了解了dataList的作用之后,就要清楚在recyclerView中数据怎么处理这些数据的呢?
前一章说了,处理数据的核心类是CoreRecyclerAdapter
因为只有它继承了RecyclerView.Adapter
,它有处理数据,并且把数据显示到UI的本领。我们在来看一个adapter。
public class PayloadActivity extends BaseActivity { ...省略一些代码 @Override protected void setUpAdapter() { RecyclerAdapter recyclerAdapter = new RecyclerAdapter(); dataListManager = new DataListManager<>(recyclerAdapter, new PayloadProvider<Flower>() { @Override public boolean areContentsTheSame(Flower oldItem, Flower newItem) { return oldItem.getFlowerId() == newItem.getFlowerId(); } @Override public Object getChangePayload(Flower oldItem, Flower newItem) { return newItem.getFlowerName(); } }); recyclerAdapter.registerBinder( new FlowerBinderWithPayload(new SimpleDividerDecoration(this, VERTICAL))); recyclerAdapter.addDataManager(dataListManager); ...省略设置布局管理器,适配器,分割线 List<Flower> dataList = new ArrayList<>(); ...省略添加数据的方法 dataListManager.set(dataList); } //点击事件定义在XML中 public void add(View view) { Flower flower = new Flower(itemIndex, "Flower " + itemIndex); dataListManager.add(flower); itemIndex++; } //点击事件定义在XML中 public void remove(View view) { if (dataListManager.getCount() == 0) { Toast.makeText(this, "No items in adapter to remove!", Toast.LENGTH_SHORT).show(); return; } if (random.nextBoolean()) { dataListManager.remove(0); } else { int indexToRemove = random.nextInt(dataListManager.getCount() - 1); dataListManager.remove(indexToRemove); } } //点击事件定义在XML中 public void update(View view) { if (dataListManager.getCount() == 0) { Toast.makeText(this, "No items in adapter to update!", Toast.LENGTH_SHORT).show(); return; } int indexToUpdate = random.nextBoolean() ? 0 : random.nextInt(dataListManager.getCount() - 1); Flower flower = dataListManager.get(indexToUpdate); flower.setFlowerName("Updated Flower " + flower.getFlowerId()); dataListManager.set(indexToUpdate, flower); }}
效果图:
完整代码可以在作者的sample中看到。这个例子用就到了DiffUtil
。
在setUpAdapter()方法中是第一次Load数据,之后点击Update按钮就可以实现优雅的更新了。我们通过set,add等各种各样的添加方法,将数据设置给BaseDataManager
里面的 List<M> dataList = new ArrayList<>();
而CoreRecyclerAdapter
又维护了数据管理器的集合 List<BaseDataManager> dataManagers = new ArrayList<>();
所以我们通过遍历dataManagers
可以得到每个BaseDataManager
里面dataList的数据。这里还是看RecyclerView.adapter的几个核心方法。
getItemCount()
@Override public final int getItemCount() { int itemCount = 0; for (BaseDataManager dataManager : dataManagers) { //这里获取每个集合中的数量 itemCount += dataManager.size(); } return itemCount; }
这里将数据相加返回给adapter。
onCreateViewHolder
@Override public final ItemViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { //直接根据getItemViewType返回的position来获取对应的ViewType来创建布局 return binders.get(viewType) .createViewHolder(LayoutInflater.from(parent.getContext()), parent, actionListener); }
关于getItemViewType
之前已经说了,这里就不再说了。
onBindViewHolder
@Override public final void onBindViewHolder(ItemViewHolder holder, int adapterPosition) { onBindViewHolder(holder, adapterPosition, null); } @Override public final void onBindViewHolder(ItemViewHolder holder, int adapterPosition, List<Object> payloads) { //根据传入的holder,获取当前的ItemBinder ItemBinder baseBinder = binders.get(holder.getItemViewType()); int totalCount = 0; for (BaseDataManager dataManager : dataManagers) { totalCount += dataManager.getCount(); Log.w("kim", "adapterPosition= " + adapterPosition + "total count=" + totalCount); //比如一共有13个item,position是从0开始的,所以position最大是12,而totalCount是13 if (adapterPosition < totalCount) { int itemPosition = getItemPositionInManager(adapterPosition); if (dataManager instanceof DataGroupManager) { dataManager = ((DataGroupManager) dataManager).getDataManagerForPosition(itemPosition); } //noinspection unchecked holder.setItem(dataManager.getItem(itemPosition)); break; } } if (null == payloads || payloads.size() == 0) { //noinspection unchecked baseBinder.bindViewHolder(holder, holder.getItem()); } else { //noinspection unchecked baseBinder.bindViewHolder(holder, holder.getItem(), payloads); } }
这里写了两个onBindViewHolder
是因为用了DiffUtil
。于是这里根据不同的viewtype,然后通过holder.setItem
把数据给了ItemBinder
.这样客户端就可以安心的让View加载数据了。
公共库里面本来就有Glide,Picasso这种优秀的图片加载框架,可是项目中有些人偏偏要去用个Bitmap对象,然后自己去采样生成图片,感觉自己吊炸天。ε=(´ο`*)))唉。
╭︿︿︿╮
{/ # # /}
( (oo) )
︶︶︶
- 学习MultiViewAdapter——3
- 学习MultiViewAdapter——1
- 学习MultiViewAdapter——2
- 学习MultiViewAdapter——4
- 【学习】——高效学习
- 机器学习—学习笔记
- CGAL——学习3
- Android学习笔记3——学习intent
- Genesis-3D学习手册——1.学习界面
- XMPP学习——3、XMPP协议学习补充
- XMPP学习——3、XMPP协议学习补充
- 机器学习(3)——SVM学习
- NLP深度学习 —— CS224学习笔记3
- 机器学习(3)——监督学习
- 人工智障学习笔记——机器学习(3)决策树
- Silverlight学习笔记—3
- 网格的学习—3
- PyTorch学习3—神经网络
- caffe源码阅读之Blob
- C交换数组
- CodeForces
- MySql查询之单表查询 --附练习素材
- @RequestParam的使用
- 学习MultiViewAdapter——3
- 测试zookeeper服务报"java.net.ConnectException: 拒绝连接..错误
- Bootstrap使用经验(一) 网格系统
- Wannafly挑战赛3-A-珂学送分(概率dp)
- springmvc视图解析器配置
- 关于中断try-catch-finally的小结
- JAVA_排序算法_时间测试_方法调用
- 人脸检测SSD 记录
- 基于MATLAB图像预处理——图像增强