美文网首页RecyclerView源码分析Android开发Android进阶之路
RecycleView源码分析(四)LayoutManager源

RecycleView源码分析(四)LayoutManager源

作者: ZSACH | 来源:发表于2021-12-17 15:50 被阅读0次

    上篇测绘流程的核心逻辑传递到LayoutManager中,本篇我们会详细分析LinearLayoutManager的源码,分析完成这个测绘流程,并且为以后自己实现LayoutManager作准备。

    几个比较重要的方法。

    1. generateDefaultLayoutParams()

    LinearLayoutManager继承了RecyclerView.LayoutManager,RecyclerView.LayoutManager是一个抽象类。只有一个抽象方法generateDefaultLayoutParams是必须实现的。

       public abstract LayoutParams generateDefaultLayoutParams();
    

    这个方法是设置每个item默认的LayoutParams的,我们必须指定这个方法的实现。
    LinearLayoutManager的实现比较简单。横竖向都是自适应的。

        @Override
        public RecyclerView.LayoutParams generateDefaultLayoutParams() {
            return new RecyclerView.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
                    ViewGroup.LayoutParams.WRAP_CONTENT);
        }
    

    2. onLayoutChildren

    在分析RecycleView的测绘流程的时候,控件的测绘主要是调用了LayoutManager的onLayoutChildren方法,这是一个入口,内部完成了各个item的measure和layout,实现第一次item的填充。里面有两个参数,第一个就是RecycleView的复用item的重头戏控件Recycler,用于内部回收复用的方式获取view。第二个是保存各种测绘信息的集中单元。

        public void onLayoutChildren(Recycler recycler, State state) {
                Log.e(TAG, "You must override onLayoutChildren(Recycler recycler, State state) ");
            }
    

    3. onMeasure()和isAutoMeasureEnabled()

    onMeasure()和isAutoMeasureEnabled()上一篇也说过了,不要同时实现它们的逻辑,两个方法重写是互斥的。isAutoMeasureEnabled()是系统提供的自动测绘,onMeasure()如果重写我们自己的测量流程。如果重写isAutoMeasureEnabled()返回true就不应该重写onMeasure()。如果没有重写isAutoMeasureEnabled(),默认是返回false,这时应该重写onMeasure()。

    4. canScrollHorizontally()、canScrollVertically()、scrollHorizontallyBy()、scrollVerticallyBy()

    滑动系列方法,前两个是是否可以水平竖直方向进行滑动。后两个是滑动过程中进行处理的方法。只有在前两个方法中放开,也就是返回true,RecycleView才会把touchEvent下放到LayoutManager里面。后两个方法随着滑动不但要实现view的回收和复用,还要进行新view的填充,和滚动出屏幕的回收。

    如果要自己实现一个LayoutManager(后面的章节我们会自己实现一个LayoutManager),我们重写上面的几个方法就可以完成了。所以在分析系统的LinearLayoutManager时,我们着重分析上面的几个方法,即可了解大部分功能。

    LayoutManager运行的整体思路

    1. 通过isAutoMeasureEnabled()判断是否使用自动测量,没有开启那么就靠LayoutManager#onMeasure()进行测量,这时我们就要自己去实现了。如果开启了,就走系统的dispatchLayoutStep系列方法,这时我们不需要自定义onMeasure()。
    2. 通过onLayoutChildren完成第一次的填充和后面的notify刷新,内部会通过计算可用面积,只填充显示区域下的各个item。这时第一屏的效果已经完成了。
    3. 我们滑动RecycleView,通过canScrollHorizontally()、canScrollVertically()、scrollHorizontallyBy()、scrollVerticallyBy()一些列方法,配置滑动下的反应。完成动态的填充,和不可见item的回收,新item的复用。
      LayoutManager内分工比较明确,onLayoutChildren负责整屏的填充,scrollHorizontallyBy()、scrollVerticallyBy()处理滑动过程中的填充。两种填充方式都有了。

    LayoutManager集成了RV的滑动处理和测量布局处理。实际上底层就是测量布局的处理,因为滑动过程中也需要重新进行测量和布局。

    本篇只分析onLayoutChildren整屏填充部分,下一篇分析滑动部分。

    onLayoutChildren整屏填充

    前置条件:state.isPreLayout()为false,上文讲了和是否完成第一次测量和是否有动画相关。所以这个条件判断为true下的代码会在动画篇章讲解。

    onLayoutChildren的代码比较多,但是从整体看,只有两个步骤

    1. 确认锚点信息
    2. 根据锚点信息进行测量布局

    确认锚点信息步骤

        //处理SavedState数据
        if (mPendingSavedState != null || mPendingScrollPosition != RecyclerView.NO_POSITION) {
                if (state.getItemCount() == 0) {
                    removeAndRecycleAllViews(recycler);
                    return;
                }
            }
            if (mPendingSavedState != null && mPendingSavedState.hasValidAnchor()) {
                mPendingScrollPosition = mPendingSavedState.mAnchorPosition;
            }
    
            ensureLayoutState();
            mLayoutState.mRecycle = false;
            resolveShouldLayoutReverse();
    
            final View focused = getFocusedChild();
            if (!mAnchorInfo.mValid || mPendingScrollPosition != RecyclerView.NO_POSITION
                    || mPendingSavedState != null) {
                mAnchorInfo.reset();
                mAnchorInfo.mLayoutFromEnd = mShouldReverseLayout ^ mStackFromEnd;
                // 初始化锚点信息
                updateAnchorInfoForLayout(recycler, state, mAnchorInfo);
                mAnchorInfo.mValid = true;
            } else if (focused != null && (mOrientationHelper.getDecoratedStart(focused)
                            >= mOrientationHelper.getEndAfterPadding()
                    || mOrientationHelper.getDecoratedEnd(focused)
                    <= mOrientationHelper.getStartAfterPadding())) {
                mAnchorInfo.assignFromViewAndKeepVisibleRect(focused, getPosition(focused));
            }
    

    我们依次看下需要分析的点

    1. mPendingSavedState和mPendingScrollPosition
      mPendingSavedStates是缓存恢复的数据,通过它可以恢复到之前的状态。通常发生在销毁重建的过程。mPendingScrollPosition是通过scrollToPosition设置的。也就是外部要求滚动到的位置。没有设置默认值就是RecyclerView.NO_POSITION。

    2. resolveShouldLayoutReverse()
      这个方法处理了布局处理的方向,从正方向或者反方向开始填充。如果是水平方向并且是从右往左的阅读方向(比如阿拉伯风格)默认就是开启了反转方向。mShouldReverseLayout为true则是反方向绘制,从底部或者右侧开始绘制。false则相反。

      private void resolveShouldLayoutReverse() {
          // A == B is the same result, but we rather keep it readable
          if (mOrientation == VERTICAL || !isLayoutRTL()) {
              mShouldReverseLayout = mReverseLayout;
          } else {
              //水平并且从右到左的方向,默认开启反转布局
              mShouldReverseLayout = !mReverseLayout;
          }
      }
      
    3. mLayoutFromEnd

      首先是 mAnchorInfo.mLayoutFromEnd的设置。只有mShouldReverseLayout和 mStackFromEnd不同时,才为true。
      mStackFromEnd是通过setStackFromEnd设置的,表示是否初始定位是否在数据末端开始,如果为true那么就从最后一条数据开始显示。 mAnchorInfo.mLayoutFromEnd为true,形象的表示就是是在反方向开始进行布局。必入垂直方向,就是屏幕下端开始布局。

    1. updateAnchorInfoForLayout锚点配置
      锚点配置在updateAnchorInfoForLayout(recycler, state, mAnchorInfo)方法中。
      首先看下AnchorInfo,这个类就是存储锚点的信息,看下内部的具体细节。

      class AnchorInfo {
          OrientationHelper mOrientationHelper;
          int mPosition;
          int mCoordinate;
          boolean mLayoutFromEnd;
          boolean mValid;
         }
      
      属性 意义
      mOrientationHelper 是一个帮助类,里面有很多测量布局帮助方法。
      mPosition 就是锚点的锚,具体就是屏幕上第一个position
      mCoordinate 表示绘制起点,初始化就是顶部底部的padding
      mLayoutFromEnd 表示是否从定位到屏幕底部
      mValid 锚点是否可用

      updateAnchorInfoForLayout方法内部具体设置实现了锚点的配置。

      private void updateAnchorInfoForLayout(RecyclerView.Recycler recycler, RecyclerView.State state,
              AnchorInfo anchorInfo) {
          if (updateAnchorFromPendingData(state, anchorInfo)) {
              return;
          }
      
          if (updateAnchorFromChildren(recycler, state, anchorInfo)) {
              return;
          }
          //设置默认的起点,也就是上下边距
          anchorInfo.assignCoordinateFromPadding();
          //默认的锚点位置如果是从顶部布局就是第一个数据,从底部就是最后一个数据。
          anchorInfo.mPosition = mStackFromEnd ? state.getItemCount() - 1 : 0;
      }
      
      void assignCoordinateFromPadding() {
              mCoordinate = mLayoutFromEnd
                      ? mOrientationHelper.getEndAfterPadding()
                      : mOrientationHelper.getStartAfterPadding();
      }
      

      大体结构:可以看出先通过peding的数据设置锚点,内部处理了mPendingScrollPosition的数据给锚点。再通过各个children设置锚点。最后都没有成功配置,设置默认的锚点。

      1. 通过mPending数据设置锚点
      private boolean updateAnchorFromPendingData(RecyclerView.State state, AnchorInfo anchorInfo) {
          if (state.isPreLayout() || mPendingScrollPosition == RecyclerView.NO_POSITION) {
              return false;
          }
         
          if (mPendingScrollPosition < 0 || mPendingScrollPosition >= state.getItemCount()) {
               // mPendingScrollPosition判断是否合法
              mPendingScrollPosition = RecyclerView.NO_POSITION;
              mPendingScrollPositionOffset = INVALID_OFFSET;
              return false;
          }
          anchorInfo.mPosition = mPendingScrollPosition;
          if (mPendingSavedState != null && mPendingSavedState.hasValidAnchor()) {
              //处理 mPendingSavedState的数据
              anchorInfo.mLayoutFromEnd = mPendingSavedState.mAnchorLayoutFromEnd;
              if (anchorInfo.mLayoutFromEnd) {
                  anchorInfo.mCoordinate = mOrientationHelper.getEndAfterPadding()
                          - mPendingSavedState.mAnchorOffset;
              } else {
                  anchorInfo.mCoordinate = mOrientationHelper.getStartAfterPadding()
                          + mPendingSavedState.mAnchorOffset;
              }
              return true;
          }
      
          if (mPendingScrollPositionOffset == INVALID_OFFSET) {
              //处理没有设置offset的情况
              View child = findViewByPosition(mPendingScrollPosition);
              if (child != null) {
                  final int childSize = mOrientationHelper.getDecoratedMeasurement(child);
                  if (childSize > mOrientationHelper.getTotalSpace()) {
                      // item does not fit. fix depending on layout direction
                      anchorInfo.assignCoordinateFromPadding();
                      return true;
                  }
                  final int startGap = mOrientationHelper.getDecoratedStart(child)
                          - mOrientationHelper.getStartAfterPadding();
                  if (startGap < 0) {
                      anchorInfo.mCoordinate = mOrientationHelper.getStartAfterPadding();
                      anchorInfo.mLayoutFromEnd = false;
                      return true;
                  }
                  final int endGap = mOrientationHelper.getEndAfterPadding()
                          - mOrientationHelper.getDecoratedEnd(child);
                  if (endGap < 0) {
                      anchorInfo.mCoordinate = mOrientationHelper.getEndAfterPadding();
                      anchorInfo.mLayoutFromEnd = true;
                      return true;
                  }
                  anchorInfo.mCoordinate = anchorInfo.mLayoutFromEnd
                          ? (mOrientationHelper.getDecoratedEnd(child) + mOrientationHelper
                          .getTotalSpaceChange())
                          : mOrientationHelper.getDecoratedStart(child);
              } else { // item is not visible.
                  if (getChildCount() > 0) {
                      // get position of any child, does not matter
                      int pos = getPosition(getChildAt(0));
                      anchorInfo.mLayoutFromEnd = mPendingScrollPosition < pos
                              == mShouldReverseLayout;
                  }
                  anchorInfo.assignCoordinateFromPadding();
              }
              return true;
          }
          // override layout from end values for consistency
          anchorInfo.mLayoutFromEnd = mShouldReverseLayout;
          // if this changes, we should update prepareForDrop as well
          if (mShouldReverseLayout) {
              anchorInfo.mCoordinate = mOrientationHelper.getEndAfterPadding()
                      - mPendingScrollPositionOffset;
          } else {
              anchorInfo.mCoordinate = mOrientationHelper.getStartAfterPadding()
                      + mPendingScrollPositionOffset;
          }
          return true;
      }
      

      这个方法主要处理通过scrollToPositionWithOffset(int position, int offset)方法传入的数据的处理。也就是指定滚动位置和offset的处理点。具体逻辑比较清晰,这里就不细讲了。

      1. 通过children数据设置锚点
        这里主要通过已有的childer,根据绘制从底部还是顶部,找到一个最接近的锚点。应用于notify系列方法进行刷新数据。因为当前已经有children了,所以直接找屏幕上显示的第一个view作为锚,以保存当前滑动的位置。我们自己定义的LayoutManager,当弹起软键盘时,如果没有通过children数据设置锚点。因为RecyclerView重新绘制,会回到初始进行刷新,而不是当前已经滑动到的位置。

      2. 默认锚点
        上面两个步骤都没有确认锚点,那么就会使用默认的锚点。

         anchorInfo.assignCoordinateFromPadding();
         anchorInfo.mPosition = mStackFromEnd ? state.getItemCount() - 1 : 0;
      

      短短的两行代码,第一行从方法名可以看出是设置绘制起点为padding的部分,这里也说明了RecycleView的padding属性怎么生效的。第二行设置了锚点的位置,如果setStackFromEnd设置了数据显示方向,如果是屏幕底部(true)第一屏显示的就是最后一条数据。如果屏幕顶部(false 默认)显示的就是第一条数据。

    经过上面三个步骤,锚点就确定完了。也就是起始绘制的数据index就确认了。接下来就需要确认绘制数据和调用fill进行填充了。

    确认绘制数据

    确认绘制数据主要是通过updateLayoutStateToFillStart(mAnchorInfo)和updateLayoutStateToFillEnd(mAnchorInfo)两个方法进行的。

            detachAndScrapAttachedViews(recycler);
            mLayoutState.mInfinite = resolveIsInfinite();
            mLayoutState.mIsPreLayout = state.isPreLayout();
            mLayoutState.mNoRecycleSpace = 0;
            if (mAnchorInfo.mLayoutFromEnd) {
                // fill towards start
                updateLayoutStateToFillStart(mAnchorInfo);
                mLayoutState.mExtraFillSpace = extraForStart;
                fill(recycler, mLayoutState, state, false);
                startOffset = mLayoutState.mOffset;
                final int firstElement = mLayoutState.mCurrentPosition;
                if (mLayoutState.mAvailable > 0) {
                    extraForEnd += mLayoutState.mAvailable;
                }
                // fill towards end
                updateLayoutStateToFillEnd(mAnchorInfo);
                mLayoutState.mExtraFillSpace = extraForEnd;
                mLayoutState.mCurrentPosition += mLayoutState.mItemDirection;
                fill(recycler, mLayoutState, state, false);
                endOffset = mLayoutState.mOffset;
    
                if (mLayoutState.mAvailable > 0) {
                    // end could not consume all. add more items towards start
                    extraForStart = mLayoutState.mAvailable;
                    updateLayoutStateToFillStart(firstElement, startOffset);
                    mLayoutState.mExtraFillSpace = extraForStart;
                    fill(recycler, mLayoutState, state, false);
                    startOffset = mLayoutState.mOffset;
                }
            } else {
                // fill towards end
                updateLayoutStateToFillEnd(mAnchorInfo);
                mLayoutState.mExtraFillSpace = extraForEnd;
                fill(recycler, mLayoutState, state, false);
                endOffset = mLayoutState.mOffset;
                final int lastElement = mLayoutState.mCurrentPosition;
                if (mLayoutState.mAvailable > 0) {
                    extraForStart += mLayoutState.mAvailable;
                }
                // fill towards start
                updateLayoutStateToFillStart(mAnchorInfo);
                mLayoutState.mExtraFillSpace = extraForStart;
                mLayoutState.mCurrentPosition += mLayoutState.mItemDirection;
                fill(recycler, mLayoutState, state, false);
                startOffset = mLayoutState.mOffset;
    
                if (mLayoutState.mAvailable > 0) {
                    extraForEnd = mLayoutState.mAvailable;
                    // start could not consume all it should. add more items towards end
                    updateLayoutStateToFillEnd(lastElement, endOffset);
                    mLayoutState.mExtraFillSpace = extraForEnd;
                    fill(recycler, mLayoutState, state, false);
                    endOffset = mLayoutState.mOffset;
                }
            }
    
         
            if (!state.isPreLayout()) {
                mOrientationHelper.onLayoutComplete();
            } else {
                mAnchorInfo.reset();
            }
            mLastStackFromEnd = mStackFromEnd;
    
    1. 通过detachAndScrapAttachedViews方法detach所有的child。可以理解为detach掉所有add的字View,恢复到了一个无字View的初始状态,在后面的布局中统一逻辑进行新的布局。这部分和回收有关,后面的章节讲回收复用会详细讲这部分。包括detach和remove的区别。
    2. 通过mLayoutFromEnd判断填充的方向,上面也提到过这个变量。他是从mAnchorInfo.mLayoutFromEnd = mShouldReverseLayout ^ mStackFromEnd设置的。如果mLayoutFromEnd为true就是代表从末端开始填充,垂直布局就是从底部,水平布局就是从右侧。
      如果从末端布局,先通过updateLayoutStateToFillStart配置,调用fill方法往反方向初始段布局,再通过updateLayoutStateToFillEnd配置往末端布局,同样通过fill进行末端填充。从首段进行填充刚好相反。
      为什么会是这样的设计呢?而不是直接从底部开始
      我们通过反证法验证这个问题,如果是从末端填充,我们这里通过垂直方向,也就是从屏幕底部开始布局举例。
      1D37112C-68F4-4306-AA3B-65E7C0EC0357.png
      在这种情况,我们就需要确定距离屏幕顶部有几个可以填充的,才可以确定自己的位置,因为顶部填充不满剩余的空间,我们直接从底部绘制是错误,会开天窗。
      B7438AE2-9773-4B0D-9FBF-7C7AC7470356.png
      所以通过上面的分析我们知道为什么要这么设计了。相反,从首部开始填充的逻辑先填充下侧,因为这时第一个位置肯定是在屏幕最顶部的。填充完下侧再填充上侧的,这时没有地方填充了,但是如果我们通过scrollToPositionWithOffset方法设置offset往上侧填充就可以有空间继续了。

    调用fill进行填充

    确认完锚点和绘制数据后,接下来就是重头戏了,通过fill进行填充,这是一个很重要的方法

    fill(recycler, mLayoutState, state, false)

    通过他的参数可以看出,recycler是RecycleView提供复用回收的统一控件。mLayoutState, state包含了绘制的各种参数,包括起始点、方向、可用空间等。所以这个方法大体的结构,通过recycler拿到待布局的view,通过参数进行填充。我们看下具体的代码。

        int fill(RecyclerView.Recycler recycler, LayoutState layoutState,
                RecyclerView.State state, boolean stopOnFocusable) {
            // max offset we should set is mFastScroll + available
            final int start = layoutState.mAvailable;
            if (layoutState.mScrollingOffset != LayoutState.SCROLLING_OFFSET_NaN) {
            //如果产生了滑动
                if (layoutState.mAvailable < 0) {
                    layoutState.mScrollingOffset += layoutState.mAvailable;
                }
                // 统一进行回收
                recycleByLayoutState(recycler, layoutState);
            }
            int remainingSpace = layoutState.mAvailable + layoutState.mExtraFillSpace;
            LayoutChunkResult layoutChunkResult = mLayoutChunkResult;
            while ((layoutState.mInfinite || remainingSpace > 0) && layoutState.hasMore(state)) {
                
                layoutChunkResult.resetInternal();
                if (RecyclerView.VERBOSE_TRACING) {
                    TraceCompat.beginSection("LLM LayoutChunk");
                }
                // 根据可用空间进行布局
                layoutChunk(recycler, state, layoutState, layoutChunkResult);
                if (RecyclerView.VERBOSE_TRACING) {
                    TraceCompat.endSection();
                }
                if (layoutChunkResult.mFinished) {
                    break;
                }
                layoutState.mOffset += layoutChunkResult.mConsumed * layoutState.mLayoutDirection;
                if (!layoutChunkResult.mIgnoreConsumed || layoutState.mScrapList != null
                        || !state.isPreLayout()) {
                     // 布局了新的view,减少可用空间
                    layoutState.mAvailable -= layoutChunkResult.mConsumed;
                    // we keep a separate remaining space because mAvailable is important for recycling
                    remainingSpace -= layoutChunkResult.mConsumed;
                }
    
                if (layoutState.mScrollingOffset != LayoutState.SCROLLING_OFFSET_NaN) {
                    layoutState.mScrollingOffset += layoutChunkResult.mConsumed;
                    if (layoutState.mAvailable < 0) {
                        layoutState.mScrollingOffset += layoutState.mAvailable;
                    }
                    recycleByLayoutState(recycler, layoutState);
                }
                if (stopOnFocusable && layoutChunkResult.mFocusable) {
                    break;
                }
            }
            if (DEBUG) {
                validateChildOrder();
            }
            return start - layoutState.mAvailable;
        }
    

    逻辑比较简单

    1. 首先进行回收,这部分是进行滑动后的回收工作,下一章会讲滑动的部分。
    2. 算出remainingSpace,也就是可用的布局空间,while内部的判断也是通过remainingSpace判断是否有空间可以继续填充。消耗remainingSpace是通过layoutChunkResult这个变量。layoutChunkResult使用layoutChunk方法进行赋值的,所以主要的布局逻辑在layoutChunk中,这个也是一个很重要的方法。内部完成了指定postion的view测量和布局。

    我们看下具体的layoutChunk代码:

    void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state,
                LayoutState layoutState, LayoutChunkResult result) {
            // 通过Recycler通过复用回收拿到下一个要填充postion的view
            View view = layoutState.next(recycler);
            if (view == null) {
                result.mFinished = true;
                return;
            }
            RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) view.getLayoutParams();
            if (layoutState.mScrapList == null) {
                if (mShouldReverseLayout == (layoutState.mLayoutDirection
                        == LayoutState.LAYOUT_START)) {
                    addView(view);
                } else {
                    addView(view, 0);
                }
            } else {
                if (mShouldReverseLayout == (layoutState.mLayoutDirection
                        == LayoutState.LAYOUT_START)) {
                    addDisappearingView(view);
                } else {
                    addDisappearingView(view, 0);
                }
            }
            // 测量
            measureChildWithMargins(view, 0, 0);
            result.mConsumed = mOrientationHelper.getDecoratedMeasurement(view);
            int left, top, right, bottom;
            if (mOrientation == VERTICAL) {
                if (isLayoutRTL()) {
                    right = getWidth() - getPaddingRight();
                    left = right - mOrientationHelper.getDecoratedMeasurementInOther(view);
                } else {
                    left = getPaddingLeft();
                    right = left + mOrientationHelper.getDecoratedMeasurementInOther(view);
                }
                if (layoutState.mLayoutDirection == LayoutState.LAYOUT_START) {
                    bottom = layoutState.mOffset;
                    top = layoutState.mOffset - result.mConsumed;
                } else {
                    top = layoutState.mOffset;
                    bottom = layoutState.mOffset + result.mConsumed;
                }
            } else {
                top = getPaddingTop();
                bottom = top + mOrientationHelper.getDecoratedMeasurementInOther(view);
    
                if (layoutState.mLayoutDirection == LayoutState.LAYOUT_START) {
                    right = layoutState.mOffset;
                    left = layoutState.mOffset - result.mConsumed;
                } else {
                    left = layoutState.mOffset;
                    right = layoutState.mOffset + result.mConsumed;
                }
            }
            // 布局
            layoutDecoratedWithMargins(view, left, top, right, bottom);
            
            }
            // Consume the available space if the view is not removed OR changed
            if (params.isItemRemoved() || params.isItemChanged()) {
                result.mIgnoreConsumed = true;
            }
            result.mFocusable = view.hasFocusable();
        }
    
    1. 首先提供next获取下一个要操作的view,这里直接调用了recycler.getViewForPosition方法,内部封装了复用的逻辑,下面的章节会细致的讲解。mItemDirection这里比较巧妙,他表示布局的方向,只能是1或者-1。
        View next(RecyclerView.Recycler recycler) {
                if (mScrapList != null) {
                    return nextViewFromScrapList();
                }
                final View view = recycler.getViewForPosition(mCurrentPosition);
                mCurrentPosition += mItemDirection;
                return view;
            }
    
    1. 拿到view后,我们终于找到了子view测量布局的地方,通过measureChildWithMargins进行测量,通过layoutDecoratedWithMargins布局。

    这样一步一步,直到RecycleView的可用空间填充满了,这时RecycleView就完成了第一屏的填充。

    总结

    1. LayoutManager的职责主要集中在整屏布局填充、滑动布局填充、滑动处理、item测量布局等功能,我们实现自己的LayoutManager时,也要实现这些功能
    2. 整屏填充的逻辑先确认锚点、确认布局参数,最终利用上面产生的数据进行填充。
    3. 填充填充可用区域的大小,也就是限制在RecyclerView的宽高内。

    分析完LayoutManager的源码。并结合前两章的测绘流程,应该对整体的测绘,从表到里都有了很深的理解。

    下一篇我们讲解RecycleView的滑动机制,分析滑动的处理和这是布局如何填充逻辑。

    相关文章

      网友评论

        本文标题:RecycleView源码分析(四)LayoutManager源

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