RecyclerView源码分析

public class RecyclerView extends ViewGroup implements ScrollingView, NestedScrollingChild2, NestedScrollingChild3

RecyclerView实现了NestedScrollingChild2, NestedScrollingChild3接口。因为NestedScrollingChild3继承自NestedScrollingChild2,NestedScrollingChild2又继承自NestedScrollingChild,
所以我们只看NestedScrollingChild接口即可。NestedScrollingChild主要是实现了嵌套滑动的功能。
那什么是嵌套滑动呢?即:子View在处理事件的时候,通过回调让父容器也可以处理滚动
嵌套逻辑相关类:
NestedScrollView 实现 NestedScrollingParent、NestedScrollingChild
NestedScrollingParent、NestedScrollingChild
NestedScrollingParentHelper、NestedScrollingChildHelper

嵌套逻辑相关方法:
onNestedPreScroll()、onNestedScroll()
onNestedPreFling()、onNestedFling()

都是在子View中处理的,通过回调方法给的, 没有再走传统的事件分发流程

那我们看下嵌套滑动的流程:

--> RecyclerView.onTouchEvent(MotionEvent e)
       // MotionEvent.ACTION_DOWN
   --> startNestedScroll(nestedScrollAxis, TYPE_TOUCH);
           //找到实现NestedScrollingParent的父控件
       --> return getScrollingChildHelper().startNestedScroll(axes, type);
       // MotionEvent.ACTION_MOVE。
   --> if (dispatchNestedPreScroll(canScrollHorizontally ? dx : 0,canScrollVertically ? dy : 0, mReusableIntPair, mScrollOffset, TYPE_TOUCH)) {
       --> return getScrollingChildHelper().dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow, type);
           --> return dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow, TYPE_TOUCH);@NestedScrollingChildHelper
               --> ViewParentCompat.onNestedPreScroll(parent, mView, dx, dy, consumed, type);
                       // 先让实现NestedScrollingParent的父控件的onNestedPreScroll()
                   --> ((NestedScrollingParent) parent).onNestedPreScroll(target, dx, dy, consumed);

          dx -= mReusableIntPair[0];
          dy -= mReusableIntPair[1];
          mNestedOffsets[0] += mScrollOffset[0];
          mNestedOffsets[1] += mScrollOffset[1];
          getParent().requestDisallowInterceptTouchEvent(true);
       }
       // 将父控件移动的距离减去,剩下的就是自己需要滑动的距离了。
   --> mLastTouchX = x - mScrollOffset[0];
       mLastTouchY = y - mScrollOffset[1];
   --> if (scrollByInternal(canScrollHorizontally ? dx : 0, canScrollVertically ? dy : 0, e)) {
       --> scrollStep(x, y, mReusableIntPair);
               // mLayout是我们设置的LayoutManager。这里以LinearLayoutManager横向滚动为例。
           --> consumedX = mLayout.scrollHorizontallyBy(dx, mRecycler, mState);
               --> return scrollBy(dx, recycler, state);@LinearLayoutManager
                       // fill方法非常关键,recyclerView的缓存和复用代码都在这里。。。
                   --> final int consumed = mLayoutState.mScrollingOffset + fill(recycler, mLayoutState, state, false);
                           //缓存
                       --> recycleByLayoutState(recycler, layoutState);
                           //复用
                       --> layoutChunk(recycler, state, layoutState, layoutChunkResult);
                           --> View view = layoutState.next(recycler);
                               --> final View view = recycler.getViewForPosition(mCurrentPosition);
                                   --> return getViewForPosition(position, false);@RecyclerView
                                           // 主要的复用代码
                                       --> return tryGetViewHolderForPositionByDeadline(position, dryRun, FOREVER_NS).itemView;
                               // 测量,padding、margin、inset(分割线的空间)
                           --> measureChildWithMargins(view, 0, 0);
      
       --> dispatchNestedScroll(consumedX, consumedY, unconsumedX, unconsumedY, mScrollOffset, TYPE_TOUCH, mReusableIntPair);
           --> getScrollingChildHelper().dispatchNestedScroll(dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, offsetInWindow, type, consumed);
               --> dispatchNestedScrollInternal(dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, offsetInWindow, type, consumed);@NestedScrollingChildHelper
                   --> ViewParentCompat.onNestedScroll(parent, mView, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, type, consumed);
                       // 调用实现NestedScrollingParent的父控件的onNestedScroll()
                       --> ((NestedScrollingParent) parent).onNestedScroll(target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed);
           getParent().requestDisallowInterceptTouchEvent(true);
       }

在上面代码中,我们追溯到了RecyclerView的复用逻辑的代码。那我们就继续深入了解下,RecyclerView是怎么进行复用的。

--> fill(recycler, mLayoutState, state, false)
    --> tryGetViewHolderForPositionByDeadline(position, dryRun, FOREVER_NS).itemView;@RecyclerView
             // 第①步,mChangedScrap是通过动画获取
         --> holder = getChangedScrapViewForPosition(position);
                 //两种方式获取,通过position和stableId获取。stableId是为解决闪烁问题
             --> final ViewHolder holder = mChangedScrap.get(i);

             // 第②步,holder还为null,从mAttachedScrap、mCachedViews,通过position获取。
         --> holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun);
             --> final ViewHolder holder = mAttachedScrap.get(i);
             --> final ViewHolder holder = mCachedViews.get(i);
    
             // 第③步,holder还为null,从mAttachedScrap、mCachedViews,通过stableId获取。
         --> holder = getScrapOrCachedViewForId(mAdapter.getItemId(offsetPosition), type, dryRun);
             --> final ViewHolder holder = mAttachedScrap.get(i);
             --> final ViewHolder holder = mCachedViews.get(i);
    
             // 第④步,自定义复用,用起来比较麻烦,一般不用。缓存和复用都需要自己实现
         --> final View view = mViewCacheExtension.getViewForPositionAndType(this, position, type);
         --> holder = getChildViewHolder(view);
             --> return getChildViewHolderInt(child);
                 --> return ((LayoutParams) child.getLayoutParams()).mViewHolder;
    
             // 第⑤步,holder还为null,从RecycledViewPool去找
         --> holder = getRecycledViewPool().getRecycledView(type);
                 // getRecycledViewPool() = RecycledViewPool
             --> final ScrapData scrapData = mScrap.get(viewType);
             --> final ArrayList scrapHeap = scrapData.mScrapHeap;
             --> return scrapHeap.remove(i);
    
             // 第⑥步,holder还为null,创建 ViewHolder 对象
         --> holder = mAdapter.createViewHolder(RecyclerView.this, type);
             --> final VH holder = onCreateViewHolder(parent, viewType);
    
             // 第⑦步,去处理数据
         --> bound = tryBindViewHolderByDeadline(holder, offsetPosition, position, deadlineNs);
             --> mAdapter.bindViewHolder(holder, offsetPosition);

从代码分析可知,RecyclerView的复用是通过以下五种方式: mChangedScrap、mAttachedScrap、mCachedViews、mViewCacheExtension、RecycledViewPool
我们可以将其概括成四类,也就是四级缓存。
1、mChangedScrap、mAttachedScrap:用来缓存还在屏幕内的ViewHolder,局部刷新使用,主要是为了性能考虑。
2、mCachedViews:用来缓存已经移除到屏幕外的ViewHolder。
3、mViewCacheExtension:这个的创建和缓存完全由开发者自己控制,系统是没有向这个里面缓存数据的。
4、RecycledViewPool:ViewHolder的缓存池。

--> fill(recycler, mLayoutState, state, false)
    --> recycleByLayoutState(recycler, layoutState);
        //缓存分为向上滑动和向下滑动。看一个就够了
        --> if (layoutState.mLayoutDirection == LayoutState.LAYOUT_START) {
               recycleViewsFromEnd(recycler, scrollingOffset, noRecycleSpace);
            } else {
               recycleViewsFromStart(recycler, scrollingOffset, noRecycleSpace);
               --> recycleChildren(recycler, childCount - 1, i);
                      //移除和回收view
                   --> removeAndRecycleViewAt(i, recycler);
                       --> recycler.recycleView(view);
                               // 进行mCachedViews和RecycledViewPool缓存
                           --> recycleViewHolderInternal(holder);@Recycler
                                   // mViewCacheMax = DEFAULT_CACHE_SIZE = 2。可通过setViewCacheSize(int viewCount)设置
                                   // 如果mCachedViews已经满了,则取出位置为0的view。
                               --> if (cachedViewSize >= mViewCacheMax && cachedViewSize > 0) {
                                       recycleCachedViewAt(0);
                                           // 从mCachedViews取出view,加到pool中。
                                       --> ViewHolder viewHolder = mCachedViews.get(cachedViewIndex);
                                           addViewHolderToRecycledViewPool(viewHolder, true);
                                           --> getRecycledViewPool().putRecycledView(holder);
                                                   // 通过itemType去获取ViewHolder集合。
                                               --> final int viewType = scrap.getItemViewType();
                                                   // int mMaxScrap = DEFAULT_MAX_SCRAP = 5。可通过setMaxRecycledViews(int viewType, int max)设置
                                                   final ArrayList scrapHeap = getScrapDataForType(viewType).mScrapHeap;
                                                       // 如果缓存池也满了,直接return。
                                                       if (mScrap.get(viewType).mMaxScrap <= scrapHeap.size()) {
                                                           return;
                                                       }
                                                       // 复位。将viewHolder上的数据信息清除。
                                                       scrap.resetInternal();
                                                       scrapHeap.add(scrap);
                                           mCachedViews.remove(cachedViewIndex);
                                       cachedViewSize--;
                                   }
                                   // 再将新的添加到mCachedViews中
                               --> mCachedViews.add(targetCacheIndex, holder);
                                   // mCachedViews已经满了,那就存到pool中。
                               --> addViewHolderToRecycledViewPool(holder, true);
            }

总结:
cacheView的大小默认是2,从cacheView复用viewHolder,不需要绑定数据,不需调用onBindViewHolder
缓存池的大小默认是5,从缓存池中复用viewHolder,需要重新绑定数据,需调用onBindViewHolder
如果从cacheView和缓存池中没有获取到ViewHolder,则调用onCreateViewHolder

上面我们只看到了cacheView和缓存池这两级缓存,那还有两级缓存是在哪里呢?我们来看下onMeasure和onLayout方法。

--> onMeasure(int widthSpec, int heightSpec)
    --> final boolean measureSpecModeIsExactly = widthMode == MeasureSpec.EXACTLY && heightMode == MeasureSpec.EXACTLY;
        // 如果设置的宽高模式是EXACTLY,那就直接返回。那具体是在哪里测量的呢?接着看onLayout方法
        if (measureSpecModeIsExactly || mAdapter == null) {
            return;
        }
        if (mState.mLayoutStep == State.STEP_START) {
            dispatchLayoutStep1();
        }
        dispatchLayoutStep2();
--> onLayout(boolean changed, int l, int t, int r, int b)
    --> dispatchLayout();
        -->  if (mState.mLayoutStep == State.STEP_START) {
                 // 执行dispatchLayoutStep1会设置mState.mLayoutStep值为State.STEP_LAYOUT
                 dispatchLayoutStep1();
                 --> mState.mLayoutStep = State.STEP_LAYOUT;
                 mLayout.setExactMeasureSpecsFrom(this);
                 // 执行dispatchLayoutStep2会设置mState.mLayoutStep值为State.STEP_ANIMATIONS
                 dispatchLayoutStep2();
                 --> mState.mLayoutStep = State.STEP_ANIMATIONS;
             } else if (mAdapterHelper.hasUpdates() || mLayout.getWidth() != getWidth() || mLayout.getHeight() != getHeight()) {
                 mLayout.setExactMeasureSpecsFrom(this);
                 dispatchLayoutStep2();
             } else {
                 mLayout.setExactMeasureSpecsFrom(this);
             }
             dispatchLayoutStep3();

dispatchLayoutStep1()、dispatchLayoutStep2()、dispatchLayoutStep3()都只会执行一次,dispatchLayoutStep3()只在onLayout中有调用。

--> dispatchLayoutStep1()
        // 动画前布局
    --> mViewInfoStore.addToPreLayout(holder, animationInfo);


    //具体的测量和布局都在dispatchLayoutStep2()中
--> dispatchLayoutStep2();
    --> mLayout.onLayoutChildren(mRecycler, mState);
        --> detachAndScrapAttachedViews(recycler);
            --> scrapOrRecycleView(recycler, i, v);
                --> recycler.scrapView(view);
                    -->  if (holder.hasAnyOfTheFlags(ViewHolder.FLAG_REMOVED | ViewHolder.FLAG_INVALID) || !holder.isUpdated() || canReuseUpdatedViewHolder(holder)) {
                             if (holder.isInvalid() && !holder.isRemoved() && !mAdapter.hasStableIds()) {
                                 throw new IllegalArgumentException("Called scrap view with an invalid view."
                                         + " Invalid views cannot be reused from scrap, they should rebound from"
                                         + " recycler pool." + exceptionLabel());
                             }
                             holder.setScrapContainer(this, false);
                             // 缓存到mAttachedScrap中
                             mAttachedScrap.add(holder);
                         } else {
                             if (mChangedScrap == null) {
                                 mChangedScrap = new ArrayList();
                             }
                             holder.setScrapContainer(this, true);
                             // 缓存到mChangedScrap
                             mChangedScrap.add(holder);
                         }
        --> fill(recycler, mLayoutState, state, false);


--> dispatchLayoutStep3()
        // 动画后布局
    --> mViewInfoStore.addToPostLayout(holder, animationInfo);

总结:
mAttachedScrap、mChangedScrap缓存还在屏幕内的ViewHolder

你可能感兴趣的:(RecyclerView源码分析)