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。