Recycleview四级缓存
- mAttachedScrap(屏幕内),用于屏幕内itemview快速重用,不需要重新createView和bindView
- mCacheViews(屏幕外),保存最近移出屏幕的ViewHolder,包含数据和position信息,复用时必须是相同位置的ViewHolder才能复用,应用场景在那些需要来回滑动的列表中,当往回滑动时,能直接复用ViewHolder数据,不需要重新bindView。
- mViewCacheExtension(自定义缓存),不直接使用,需要用户自定义实现,默认不实现。
- mRecyclerPool(缓存池),当cacheView满了后或者adapter被更换,将cacheView中移出的ViewHolder放到Pool中,放之前会把ViewHolder数据清除掉,所以复用时需要重新bindView。
如果多个RecyclerView之间用setRecycledViewPool(RecycledViewPool)设置同一个RecycledViewPool,他们就可以共享Item。其实RecycledViewPool的内部维护了一个Map,里面以不同的viewType为Key存储了各自对应的ViewHolder集合。可以通过提供的方法来修改内部缓存的Viewholder。
四级缓存按照顺序需要依次读取。所以完整缓存流程是:
保存缓存流程:
- 插入或是删除itemView时,先把屏幕内的ViewHolder保存至AttachedScrap中
- 滑动屏幕的时候,先消失的itemview会保存到CacheView,CacheView大小默认是2,超过数量的话按照先入先出原则,移出头部的itemview保存到RecyclerPool缓存池(如果有自定义缓存就会保存到自定义缓存里),RecyclerPool缓存池会按照itemview的itemtype进行保存,每个itemType缓存个数为5个,超过就会被回收。
获取缓存流程:
- AttachedScrap中获取,通过pos匹配holder——>获取失败,从CacheView中获取,也是通过pos获取holder缓存——>获取失败,从自定义缓存中获取缓存——>获取失败,从mRecyclerPool中获取——>获取失败,重新创建viewholder——createViewHolder并bindview。
预取功能(Prefetch)
这个功能是rv在版本25之后自带的,LinearLayoutManager的setInitialItemPrefetchCount()我们可以手动控制该功能。
功能:预取接下来可能要显示的item,在下一帧到来之前提前处理完数据,然后将得到的itemholder缓存起来,等到真正要使用的时候直接从缓存取出来即可。
刷新方法:
- notifyDataSetChanged(),刷新全部可见的item。
- notifyItemChanged(int)、notifyItemChanged(int position, @Nullable Object payload),刷新指定item。
- notifyItemRangeChanged(int,int),从指定位置开始刷新指定个item。
- notifyItemInserted(int)、notifyItemMoved(int)、notifyItemRemoved(int)。插入、移动一个并自动刷新。
- notifyItemChanged(int, Object),局部刷新。
notifyItemChanged(int position, @Nullable Object payload)
@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position, @NonNull List<Object> payloads) {
Item item = mData.get(position);
if (payloads.isEmpty()) {
super.onBindViewHolder(holder, position, payloads);
} else {
holder.itemView.setSelected(item.isSelected());
}
}
可以重写RecyclerView的另一个onBindViewHolder方法,在里面添加payloads的判空,可以作为标记(如上),也可以用来传值。
优化:
- 设置
RecyclerView.addOnScrollListener();
来在滑动过程中停止加载的操作。 - 使用
diffutil
进行局部刷新,少用全局刷新 bindViewHolder方法是在UI线程进行的,此方法不能耗时操作,不然将会影响滑动流畅性。
- item高度是固定的话,可以使用RecyclerView.setHasFixedSize(true);来避免requestLayout浪费资源。3
- 对 ItemView 设置监听器,不要对每个 Item 都new 一个 Listener,应该大家公用一个 Listener,根据 ID 来进行不同的操作,优化了对象的频繁创建带来的资源消耗。
Recylerview刷新图片闪烁:
@Override
public long getItemId(int position) {
return position;
}
mAdapter.setHasStableIds(true);
setHasStableIds:数据集中的每一项是否设置有一个唯一标识。
getItemId:给位置的项一个ID。
两个方法配合使用,相当于给ImageView加了一个tag,tag不变的话,不用重新加载图片的 ImageView 不重新加载。
也可以使用局部刷新来解决这个问题。
滑动冲突:
1、NestedScrollView嵌套RecyclerView
坑:NestScrollView嵌套RecyclerView时,事件还是会被NestScrollView消费,RecyclerView的滚动监听无效,而且会加载所有item,其内部的缓存起不到作用。
2、内部拦截:
在继承RecyclerView重写方法:
requestDisallowInterceptTouchEvent方法阻止拦截。
ScrollView 套 ViewGroup 套 RV 。所以是getParent()调用该方法。
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
Log.e("MyListView","dispatchTouchEvent·····"+ev.getAction());
switch (ev.getAction()){
case MotionEvent.ACTION_DOWN:
getParent().requestDisallowInterceptTouchEvent(true);
break;
case MotionEvent.ACTION_MOVE:
boolean a = !canScrollVertically(-1);
boolean b = ev.getY()-firstY>0;
boolean c = !canScrollVertically(1);
boolean d = ev.getY()-firstY<0;
if ( (b&&a) || (c&&d) )
getParent().requestDisallowInterceptTouchEvent(false);
break;
}
firstY = ev.getY();
boolean result =super.dispatchTouchEvent(ev);
return result;
}
3、外部拦截:
重写ScrollView的onInterceptTouchEvent方法进行判断什么时候该返回true。