美文网首页
RecyclerView源码分析

RecyclerView源码分析

作者: 壹元伍角叁分 | 来源:发表于2021-10-19 22:51 被阅读0次
    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<ViewHolder> 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<ViewHolder> 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<ViewHolder>();
                                 }
                                 holder.setScrapContainer(this, true);
                                 // 缓存到mChangedScrap
                                 mChangedScrap.add(holder);
                             }
            --> fill(recycler, mLayoutState, state, false);
    
    
    --> dispatchLayoutStep3()
            // 动画后布局
        --> mViewInfoStore.addToPostLayout(holder, animationInfo);
    

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

    相关文章

      网友评论

          本文标题:RecyclerView源码分析

          本文链接:https://www.haomeiwen.com/subject/huezoltx.html