RecyclerView 刷新踩坑记
工作中现在都是使用 RecyclerView,RecyclerView 中,经常使用到的几个刷新函数如下:
第 1 组
- notifyDataSetChanged():无参,用于通知 Adapter 数据源发生变化并刷新。更新方式是所有 item 整体刷新,是最重的刷新方式
- notifyItemChanged(int position):单参,用于通知在数据源中,位置处于 position 的 item 更新。更新方式是单个 item 整体刷新
- notifyItemInserted(int position):单参,用于通知在数据源中,位置处于 position 的 item 之前有 item 插入,插入之后,position 位置的 item 位置变成了 position + 1。不过应该注意的是
- 该方法使用时,应该首先变化数据源,再调用该方法
- 该方法使用后,并不能真正插入一个 item,还应结合 notifyItemChanged 使用,才能真正插入一条数据。这个方法只是决定了像动画这些配置。
- notifyItemRemoved(int position):单参,作用和注意事项与 notifyItemInserted 类似,不过一个是增,一个是减。
- notifyItemMoved(int fromPosition, int toPosition):双参,作用和 notifyItemRemoved、notifyItemInserted 类似,不过此方法对应的含义是移动
第 2 组
- notifyItemRangeRemoved(int positionStart, int itemCount):通知范围内的 item 被整体删除了,需要和 notifyItemRangeChanged(int positionStart, int itemCount) 配合使用。
- notifyItemRangeInserted(int positionStart, int itemCount):通知范围内的 item 整体增加,需要和 notifyItemRangeChanged(int positionStart, int itemCount) 配合使用。
- notifyItemRangeChanged(int positionStart, int itemCount):通知范围内的 item 变化了
第 3 组
形如 notifyItemChanged(int position, @Nullable Object payload) 等带上 payload 的方法,其含义都是局部刷新。与三参 onBindViewHolder 结合使用。
遇到的坑
- Android 中没有提供指定某些 position (位置并不连续) 的 item 更新的方法,我们能做的就是在绑定时进行过滤。
- 部分情况下,可能局部通知刷新并不生效,比如 activity 不在前台,并且不在栈顶时。这时候就需要调用整体刷新的方法,如 notifyDataSetChanged 和 notifyItemChanged。
- 整体刷新方法部分情况下也有可能并不生效。可能的原因有通知刷新的线程不在 UI 线程(网上的回答,真实性待验证),或者比如 activity 处在后台,处于前台的界面通知其刷新。调用了 notifyItemChanged 方法,此时并不能走到 onBindViewHolder 中,导致界面无法刷新。
- notifyDataSetChanged 方法部分情况下可能也不会生效。比如 activity 处于较深的栈底,处于栈顶的界面通知其刷新,此时调用了 notifyDataSetChanged 方法,但是 adapter 并没有走到 onBindViewHolder 方法中。此时就需要将更新的数据记录下来,放入内存或者本地存储中。在界面从栈顶退回栈底 activity 时,便可以读取数据,重走绑定流程,保证数据准确。强烈建议将数据存入本地存储中,这是最稳妥的方式。
- 在 onBindViewHolder 中,每次绑定视图时,都应考虑 item 复用的问题。双参的绑定方法不是局部刷新,应视为 item 的整体刷新。在绑定时,应该做到每一个 if 都应该有 else 分支。或者在开始绑定前重置每个视图的状态,如清空 TextView 的文本、清空 ImageView 的图像等等,需要额外注意的一点是,点击事件也需要做清空处理(本人遇到过 item 的点击事件复用导致的 BUG,排查了两天,最终确定的)。
- 如果因为业务需要,RecyclerView 的 Adapter 需要包含多个子 Adapter,子 Adapter 中又各自定义了多 viewType(即多 Adapter 和多 viewType 混用),则此种实现方式维护起来十分困难,多个 Adapter 混用时,需要维护多个数据列表(data list)。bind 时,父 Adapter 的列表 postion 需要有个转换映射的过程,增加耗时,数据量躲起来时,容易造成卡顿。多 Adapter 和多 viewType 混用的方式不推荐使用,要么多 Adapter,并使用 ConcatAdapter 连接;要么多 viewType,直接使用一个 Adapter。