(三)RecyclerView简单滑动场景分析

时间:2022-07-22
本文章向大家介绍(三)RecyclerView简单滑动场景分析,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。
场景

列表页加载完毕,一个列表页A含有 10 个 item, 轻轻向上滑动,RecyclerView 做了哪些操作?

今天结合源码分析一下这个场景

前提

文章分析基于RecyclerView API 25

仅分析上面这一个场景

额外创建几个ViewHolder 复用?

答案:

4 个,额外执行 4 次createViewHolder()方法

为什么?

总结如下:

  1. 根据源码我们知道最后一步获取 ViewHolder 的的逻辑是执行createViewHolder()方法 倒数第二步是从RecycledViewPool中获取,即只要RecycledViewPool中有 ViewHolder 了就不会在创建 ViewHolder 了
  2. 假设一屏可显示完整10个 item,因此屏幕最多可显示 10+1 个 item,即 11 个item. 因为稍微滑动一下 第一个和第十一个都只显示一部分.
  3. mCachedViews 大小为 2 因为预加载机制mCachedViews大小 +1 为 3
  4. 即 2+1+1 第一个 1: 预加载 将mCachedViews+1 第二个 1: 屏幕显示 10+1 个 item 接下来的滑动操作RecycledViewPool中都会有 ViewHolder 了
涉及的集合:

这个场景只涉及mCachedView和recycledViewPool这两个集合

当我们在屏幕上滑动的时候,移除的 ViewHolder 会首先放入mCachedViews,mCachedViews不满,ViewHolder 不会放入RecycledViewPool中

源码分析流程

预加载在onTouchEvent()方法中

mGapWorker.postFromTraversal() 开始预加载流程

public class RecyclerView extends ViewGroup implements ScrollingView, NestedScrollingChild2 {
    
    @Override
    public boolean onTouchEvent(MotionEvent e) {
        switch (action) {
            //...
            case MotionEvent.ACTION_MOVE: {
                //...
                if (mScrollState == SCROLL_STATE_DRAGGING) {
                    //...
                    if (mGapWorker != null && (dx != 0 || dy != 0)) {
                        //当你的指尖稍微一滑动 dy!=0 执行这个方法了
                        mGapWorker.postFromTraversal(this, dx, dy);
                    }
                }
            }
            break;        
    }
    }
}
final class GapWorker implements Runnable {
    
    @Override
    public void run() {
        try {
            //...
            //执行预加载
            prefetch(nextFrameNs);
        } finally {
            TraceCompat.endSection();
        }
    } 
    //
    void prefetch(long deadlineNs) {
        buildTaskList();
        //刷新任务
        flushTasksWithDeadline(deadlineNs);
    } 
    //
    private RecyclerView.ViewHolder prefetchPositionWithDeadline(RecyclerView view,
            int position, long deadlineNs) {
        //...
        RecyclerView.Recycler recycler = view.mRecycler;
        RecyclerView.ViewHolder holder;
        try {
            view.onEnterLayoutOrScroll();
            //获取 holder
            //关键代码
            holder = recycler.tryGetViewHolderForPositionByDeadline(
                    position, false, deadlineNs);
            if (holder != null) {
                if (holder.isBound() && !holder.isInvalid()) {
                    //放入mCachedViews
                    recycler.recycleView(holder.itemView);
                } else {
                    //放入recycledViewPool
                    recycler.addViewHolderToRecycledViewPool(holder, false);
                }
            }            
           //...
        } finally {
            view.onExitLayoutOrScroll(false);
        }
        return holder;
    }    
}

GapWorker 是一个 runnable,在Run() 方法被调用时,经过层层调用,最终来到prefetchPositionWithDeadline()方法

public class RecyclerView extends ViewGroup implements ScrollingView, NestedScrollingChild2 {
  
        @Nullable
        ViewHolder tryGetViewHolderForPositionByDeadline(int position,
                boolean dryRun, long deadlineNs) {
            //...
            ViewHolder holder = null;
            // 1) Find by position from scrap/hidden list/cache
            if (holder == null) {
                // 从mCachedView获取viewHolder
                holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun);
               //...
            }
            //...
            if (holder == null) {
                //...
                if (holder == null) {
                    //...
                    // 回调Adapter.createViewHolder()方法
                    holder = mAdapter.createViewHolder(RecyclerView.this, type);
                }
            }
            //...
            return holder;
        }    
}

因此 一上来prefetch 首先会 执行

createViewHolder

1.执行createViewHolder逻辑创建一个ViewHolder,顺手放入mCachedViews

2.此时ViewHolder在集合mCachedViews中,还没在屏幕显示

3.屏幕接着滑动,当layoutChunk()方法调用的时候, 直接从mCachedViews中获取,填充 item. 屏幕显示

4.再次执行步骤 1即预加载流程,如此往复,直至recycledViewPool中含有 ViewHolder 就不在创建ViewHolder 了. 当然一共循环 4 次.

这样的好处是在layoutChunk()填充 item 的时候,不用立即创建,而是用创建好的,滑动更流畅,更丝滑.

其他知识点

在一次点击事件中多次调用adapter.notifyItemChanged()方法 item 刷新几次?

一次,因为onItemRangeChanged()方法的返回值决定是否执行刷新,为 true 的条件就是list 的 size==1,

因此不管在一次点击事件中notifyItemChanged()几次,只有一次刷新.

class AdapterHelper implements OpReorderer.Callback {
  //......
     /**
       * @return True if updates should be processed.
       */
  boolean onItemRangeChanged(int positionStart, int itemCount, Object payload) {
      if (itemCount < 1) {
          return false;
      }
      mPendingUpdates.add(obtainUpdateOp(UpdateOp.UPDATE, positionStart, itemCount, payload));
      mExistingUpdateTypes |= UpdateOp.UPDATE;
      // 这里只有 size=1 的时候 return,也就是说,若是开发者adapter.notifyItemChanged()方法
      // 同时调用 2 次以上,只有一次生效.即只会刷新一次
      return mPendingUpdates.size() == 1;
  }
  //......  
}

更多内容 欢迎关注公众号