学习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这样可以获取到集合,然后对添加,删除,更新数据进行了处理,而且调用DiffUtilCallbackPayloadProvider将数据优雅的刷新。我们看到了上面写的关于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按钮就可以实现优雅的更新了。我们通过setadd等各种各样的添加方法,将数据设置给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) )
︶︶︶

原创粉丝点击