RecyclerView + DiffUtil 使用预研

背景:RecyclerView 使用notifyDataSetChanged 会导致图片闪烁

具体原因可参看:RecyclerView 体验优化及入坑总结  的入坑篇第二个问题

一、RecyclerView 局部刷新不好用

      RecyclerView 除了配置动画、布局等方便外,相比ListView ,提供了不少数据刷新方式,除了常见的notifyDataSetChanged() 全局刷新外,还提供了很多局部刷新方式,列举如下:

RecyclerView + DiffUtil 使用预研_第1张图片
图1  RecyclerView Adapter 对应刷新方式

        上述局部刷新方式,看似好用(插入、更新、移动、删除),但实际不太好用,甚至基本无用。不好用的原因在于,不知何时去选择刷新方式 ,因而还是一股脑的使用notifyDataSetChanged() ,除此之外 使用较多的场景 :页面加载更多 ,使用notifyItemRangeChanged(int ,int),替他刷新方式根本没有使用过。

       可能正因如此,Google 在 Support-v7:24:2.0 提供了DiffUtils 类,让我们正真意义上使用RecyclerView 的局部刷新。

二 、DiffUtil 介绍 及使用

        DiffUtil是 Support-v7:24:2.0 中,中更新的工具类,主要是为了配合RecyclerView使用,通过比对新、旧两个数据集的差异,生成旧数据到新数据的最小变动,然后对有变动的数据项,进行局部刷新。

   DiffUtil 核心内容 :

(1)DiffUtil.Callback  

DiffUtil.Callback  :具体用于限定数据集比对规则 ,内部主要有如下5个比较方法:

RecyclerView + DiffUtil 使用预研_第2张图片
图 2 DiffUtil.Callback 

 1)getOldListSize():旧数据集的长度;

 2)getNewListSize():新数据集的长度

 3)areItemsTheSame():判断是否是同一个item;

 4)areContentsTheSame():如果item相同,此方法用于判断是否同一个 Item 的内容也相同;

RecyclerView + DiffUtil 使用预研_第3张图片
图3  areContentsTheSame()

 5)getChangePayload() :如果item相同,内容不同,用 payLoad 记录这个 ViewHolder 中,具体需要更新那个View

    从图2知 ,getChangePayload() 默认返回 null ,即整个item 全部刷新。

RecyclerView + DiffUtil 使用预研_第4张图片
图4  getChangePayload()

    一般在getChangePayload()方法中调用super.getChangePayload() 即可,不做精细化刷新。

     如果一个item 非常复杂,存在里面某个View 数据刷新,可以利用payLoad参数来实现,对应修改点 在 onBindViewHolder(HelloViewHolder holder, int position ) 的基础上实现带参的 onBindViewHolder ,同时在getChangedPayLoad()对应实现。

从上面分析可知:areItemsTheSame()、areContentsTheSame()、getChangePayload() 分别代表了不同量级的刷新。

(2)DiffUtil.DiffResult

DiffUtil.DiffResult : 比对数据集之后,返回的差异结果 ,通过DiffUtil.calculateDiff(diffCallback)(当数据量较大时,建议放在子线程中调用)得到。

DiffUtil.DiffResult::dispatchUpdatesTo() 根据diff 数据结果,选择刷新方式。

RecyclerView + DiffUtil 使用预研_第5张图片
图5 DiffUtil.DiffResult::dispatchUpdatesTo()


 总结:使用起来比较简单 ,1)实现DiffUtil.Callback   接口 ;2)新老数据集通过DiffUtil.calculateDiff 计算得到DiffUtil.DiffResult  ;3)DiffUtil.DiffResult::dispatchUpdatesTo()  刷新数据。

三、项目使用DiffUtil遇到的问题

     在第二节中 ,已经基本介绍了DiffUtil 的使用,本节以精选页、书城等页面为例(忽略代码中边界值处理)说明 ,在使用过程中遇到的一些问题:

(1)item 判断 唯一性

       目前 ,setData ()  数据类型为  ArrayList  (多ViewType类型)如下 ,对于item 来说 ,缺少唯一性判断属性。

RecyclerView + DiffUtil 使用预研_第6张图片
图6 RowData()

       目前,使用mDisplayStyle 和id 来判断 item 是否一致。 如果item一致,则会进一步判断 item 内容是否一致(areContentsTheSame);如果item 不一致,则item 全部刷新。

图7 areItemsTheSame()

(2) areContentsTheSame() 判断过于麻烦 

   由于RowData 内 核心数据是mData (Object型),无法直接比较 实现equals ,只能在 areContentsTheSame() 内,手动实现每种ViewType 数据的equals (对象是jce协议,只能手动实现)。

手动实现的问题在于:1)jce协议新增字段,这里需要补充 ;2)新增ViewType 数据需要在这里补充;3)对应adapter 与这个callBack 关联性不大,易漏

上述三个问题,可能增加一定的维护成本

RecyclerView + DiffUtil 使用预研_第7张图片
图8  areContentsTheSame

RowDataDiffCallback() 整体实现  :

RecyclerView + DiffUtil 使用预研_第8张图片
图9  RowDataDiffCallback()

(3)VM 初始化 设置pullToRefreshRecycleView.setEnableLoadMore(true),导致页面滑到最下面

     目前,猜测 是加载更多模块焦点导致 (猜测未找出原因)

分析一下该问题 :pullToRefreshRecycleView.setEnableLoadMore(true) 设置本不应该在VM初始化时,设置,应该根据回包数据 的hasMore 来设置 。

直接将pullToRefreshRecycleView.setEnableLoadMore(true)  改成在回包时,调用 pullToRefreshRecycleView.setEnableLoadMore(hasMore) 上述问题解决


      上述3个问题,是我在使用diffutil中遇到的问题,在项目中可以使用,大家有空看看,还有什么疑问或改正意见。

你可能感兴趣的:(RecyclerView + DiffUtil 使用预研)