RecycleView LayoutManage-GridLayoutManage源码浅析

背景

项目有用到阿里的tangram3动态布局框架,有时候某些特殊需求想定制的时候会比较头疼,其中这个框架又依赖vlayout,所以你都要了解内部原理,最近看到vlayout的layoutManager相关代码,想着之前只看过LinearLayoutManager的布局流程 但是还没看过GridLayoutManager的,所以就有了这篇学习记录

首先

GridLayoutManager是继承于LinearLayoutManager
工作流程大概是:
RecyclerView.onLayout -> RecyclerView.dispatchLayout -> LinearLayoutManager.onLayoutChildren -> LinearLayoutManager.fill -> LinearLayoutManager.layoutChunk

GridLayoutManager最重要的一个方法就是layoutChunk,它主要是负责添加view和设置view的实际位置

为了方便理解我们设置以下条件:

  1. spanCount(列数)为2
  2. VERTICAL方向
  3. mReverseLayout为false(不反转 )

排除干扰代码后伪代码如下,分七步,大概了解下下面代码就行,下面拆分代码,记录这次学习记录

@Override
void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state,
                 LayoutState layoutState, LayoutChunkResult result) {
    final int otherDirSpecMode = mOrientationHelper.getModeInOther();//默认EXACTLY
    final boolean layingOutInPrimaryDirection =
            layoutState.mItemDirection == LayoutState.ITEM_DIRECTION_TAIL;//是否按顺序添加也就是 item排列方式为123456
    int count = 0;
    int remainingSpan = mSpanCount;//列数
    //1
    while (count < mSpanCount && layoutState.hasMore(state) && remainingSpan > 0) {
        int pos = layoutState.mCurrentPosition;
        final int spanSize = getSpanSize(recycler, state, pos);
        remainingSpan -= spanSize;
        if (remainingSpan < 0) {//有些一个item占两行 这时候就提前占完一行
            break; // item did not fit into this row or column
        }
        View view = layoutState.next(recycler);
        consumedSpanCount += spanSize;//消耗一个item位置
        mSet[count] = view;//存储到set数组后续用到
        count++;
    }
    int maxSize = 0;//当前行最大高度
    //2
    assignSpans(recycler, state, count, layingOutInPrimaryDirection);
    //3
    for (int i = 0; i < count; i++) {
        View view = mSet[i];
        if (layoutState.mScrapList == null) {//是否存在ScrapList缓存
            if (layingOutInPrimaryDirection) {
                addView(view);//添加进recyclerView
            } else {
                addView(view, 0);
            }
        } else {//尝试从缓存获取
            if (layingOutInPrimaryDirection) {
                addDisappearingView(view);
            } else {
                addDisappearingView(view, 0);
            }
        }
        calculateItemDecorationsForChild(view, mDecorInsets);//获取开发者自定义的mItemDecorations信息至mDecorInsets 没设置Rect都为0
        measureChild(view, otherDirSpecMode, false);//测量子view宽高
        final int size = mOrientationHelper.getDecoratedMeasurement(view);//获取view的垂直方向大小,也就是高度
        if (size > maxSize) {//maxSize初始为0 这时候赋值
            maxSize = size;
        }
    }
    //4
    // 如果子view 高度不统一 则根据子view的边距大小 按照EXACTLY模式测量,应该是子view在warp_content下 保证同一行的宽高是一样的
    for (int i = 0; i < count; i++) {
        final View view = mSet[i];
        if (mOrientationHelper.getDecoratedMeasurement(view) != maxSize) {
            final LayoutParams lp = (LayoutParams) view.getLayoutParams();
            final Rect decorInsets = lp.mDecorInsets;
            final int verticalInsets = decorInsets.top + decorInsets.bottom
                    + lp.topMargin + lp.bottomMargin;
            final int horizontalInsets = decorInsets.left + decorInsets.right
                    + lp.leftMargin + lp.rightMargin;
            final int totalSpaceInOther = getSpaceForSpanRange(lp.mSpanIndex, lp.mSpanSize);
            final int wSpec;
            final int hSpec;
            if (mOrientation == VERTICAL) {
                wSpec = getChildMeasureSpec(totalSpaceInOther, View.MeasureSpec.EXACTLY,
                        horizontalInsets, lp.width, false);
                hSpec = View.MeasureSpec.makeMeasureSpec(maxSize - verticalInsets,
                        View.MeasureSpec.EXACTLY);
            }
            measureChildWithDecorationsAndMargin(view, wSpec, hSpec, true);
        }
    }
    //消耗的高度-用于是否填充满一屏view计算
    result.mConsumed = maxSize;

    int left = 0, right = 0, top = 0, bottom = 0;
    //5
    if (mOrientation == VERTICAL) {
        if (layoutState.mLayoutDirection == LayoutState.LAYOUT_START) {//layoutState.mLayoutDirection是由锚点方向决定 一般都是LAYOUT_END
            bottom = layoutState.mOffset;
            top = bottom - maxSize;
        } else {
            top = layoutState.mOffset;//mOffset为layoutManage上一次填充后的结束点
            bottom = top + maxSize;
        }
    }
    //6
    for (int i = 0; i < count; i++) {
        View view = mSet[i];
        LayoutParams params = (LayoutParams) view.getLayoutParams();
        if (mOrientation == VERTICAL) {//确定左右开始点和结束点
            left = getPaddingLeft() + mCachedBorders[params.mSpanIndex];
            right = left + mOrientationHelper.getDecoratedMeasurementInOther(view);
        } else {
            top = getPaddingTop() + mCachedBorders[params.mSpanIndex];
            bottom = top + mOrientationHelper.getDecoratedMeasurementInOther(view);
        }
        // We calculate everything with View's bounding box (which includes decor and margins)
        // To calculate correct layout position, we subtract margins.
        //7 真正设置view实际高度的地方
        layoutDecoratedWithMargins(view, left, top, right, bottom);
    }
    Arrays.fill(mSet, null);
}

步骤1创建grid一整行的item view 加入mSet数组

    while (count < mSpanCount && layoutState.hasMore(state) && remainingSpan > 0) {
        int pos = layoutState.mCurrentPosition;
        final int spanSize = getSpanSize(recycler, state, pos);
        remainingSpan -= spanSize;
        if (remainingSpan < 0) {//有些一个item占两行 这时候就提前占完一行
            break; // item did not fit into this row or column
        }
        View view = layoutState.next(recycler);//通过缓存机制获取view,没有则创建
        consumedSpanCount += spanSize;//消耗一个item位置
        mSet[count] = view;//存储到set数组后续用到
        count++;
    }

这里的getSpanSize方法获取的就是开发者调用setSpanSizeLookup 设置item占几列的处理,

public void setSpanSizeLookup(SpanSizeLookup spanSizeLookup) {
    mSpanSizeLookup = spanSizeLookup;
}

而其中的otherDirSpecMode测量模式,默认是取LinearLayoutManager中width的模式,而它在初始化的时候设置为精确模式

    final int otherDirSpecMode = mOrientationHelper.getModeInOther();
    //LinearLayoutManager
    void setRecyclerView(RecyclerView recyclerView) {
        if (recyclerView == null) {
            mRecyclerView = null;
            mChildHelper = null;
            mWidth = 0;
            mHeight = 0;
        } else {
            mRecyclerView = recyclerView;
            mChildHelper = recyclerView.mChildHelper;
            mWidth = recyclerView.getWidth();
            mHeight = recyclerView.getHeight();
        }
        mWidthMode = MeasureSpec.EXACTLY;//默认设置
        mHeightMode = MeasureSpec.EXACTLY;//默认设置
    }

步骤2 assignSpans 设置相对一行的下标和所占的列数-后续计算有用到

private void assignSpans(RecyclerView.Recycler recycler, RecyclerView.State state, int count,
    ...
    span = 0;
    for (int i = start; i != end; i += diff) {
        View view = mSet[i];
        LayoutParams params = (LayoutParams) view.getLayoutParams();
        params.mSpanSize = getSpanSize(recycler, state, getPosition(view));
        params.mSpanIndex = span;//设置真实index
        span += params.mSpanSize;
    }
}

步骤3

  1. 往RecycleView中添加view

  2. 获取view中的Decoration(间隙)添加进入mDecorInsets(Rect)中(好像全局变量没用到,用到都是LayoutParams.mDecorInsets)

  3. 测量子view的宽高

  4. 获取到这一行最大item的高度

     for (int i = 0; i < count; i++) {
         View view = mSet[i];
         if (layoutState.mScrapList == null) {//是否存在ScrapList缓存
             if (layingOutInPrimaryDirection) {
                 addView(view);//添加进recyclerView
             } else {
                 addView(view, 0);
             }
         } else {//尝试从缓存获取
             if (layingOutInPrimaryDirection) {
                 addDisappearingView(view);
             } else {
                 addDisappearingView(view, 0);
             }
         }
         calculateItemDecorationsForChild(view, mDecorInsets);//获取开发者自定义的mItemDecorations信息至mDecorInsets 没设置Rect都为0
         measureChild(view, otherDirSpecMode, false);//测量子view宽高
         final int size = mOrientationHelper.getDecoratedMeasurement(view);//获取view的垂直方向大小,也就是高度
         if (size > maxSize) {//maxSize初始为0 这时候赋值
             maxSize = size;
         }
     }
    

步骤4 如果子view 高度不统一 则根据子view的边距大小 按照EXACTLY模式测量,保证同一行的宽高是一样的

    //
    for (int i = 0; i < count; i++) {
        final View view = mSet[i];
        if (mOrientationHelper.getDecoratedMeasurement(view) != maxSize) {
            final LayoutParams lp = (LayoutParams) view.getLayoutParams();
            final Rect decorInsets = lp.mDecorInsets;//步骤3 第二步获取的间隙
            final int verticalInsets = decorInsets.top + decorInsets.bottom
                    + lp.topMargin + lp.bottomMargin;//子view上下的间隙(开发者添加Decoration)+view的上下Margin
            final int horizontalInsets = decorInsets.left + decorInsets.right
                    + lp.leftMargin + lp.rightMargin;//同上
            final int totalSpaceInOther = getSpaceForSpanRange(lp.mSpanIndex, lp.mSpanSize);//算出父类给子view最大的宽度
            final int wSpec;
            final int hSpec;
            if (mOrientation == VERTICAL) {
                wSpec = getChildMeasureSpec(totalSpaceInOther, View.MeasureSpec.EXACTLY,
                        horizontalInsets, lp.width, false);
                hSpec = View.MeasureSpec.makeMeasureSpec(maxSize - verticalInsets,
                        View.MeasureSpec.EXACTLY);//maxSize - verticalInsets为item的内容区域
            }
            measureChildWithDecorationsAndMargin(view, wSpec, hSpec, true);
        }
    }
    //消耗的高度-用于是否填充满一屏view计算
    result.mConsumed = maxSize;

步骤5 根据锚点方向确定子view的上下坐标,一般情况都走2的逻辑

    if (mOrientation == VERTICAL) {//1
        if (layoutState.mLayoutDirection == LayoutState.LAYOUT_START) {//layoutState.mLayoutDirection是由锚点方向决定 初始化首屏是LAYOUT_END
            bottom = layoutState.mOffset;
            top = bottom - maxSize;
        } else {//2
            top = layoutState.mOffset;//mOffset为layoutManage上一次填充后的结束点
            bottom = top + maxSize;
        }
    }

步骤6 确定left和right坐标

    for (int i = 0; i < count; i++) {
        View view = mSet[i];
        LayoutParams params = (LayoutParams) view.getLayoutParams();
        if (mOrientation == VERTICAL) {//确定左右开始点和结束点
            left = getPaddingLeft() + mCachedBorders[params.mSpanIndex];
            right = left + mOrientationHelper.getDecoratedMeasurementInOther(view);//获取width+decorate的left和right+左右margin
        }
        //真正设置view实际高度的地方
        layoutDecoratedWithMargins(view, left, top, right, bottom);
    }

其中mCachedBorders是一个一维数组,以spanCount为2 设备宽度为1080为例,它里面存储这[0,540,1080]
调用链:
LinearLayoutManager.onLayoutChildren -> GridLayoutManager.onAnchorReady -> GridLayoutManager.updateMeasurements - > GridLayoutManager.calculateItemBorders

private void calculateItemBorders(int totalSpace) {
    mCachedBorders = calculateItemBorders(mCachedBorders, mSpanCount, totalSpace);
}

static int[] calculateItemBorders(int[] cachedBorders, int spanCount, int totalSpace) {
    if (cachedBorders == null || cachedBorders.length != spanCount + 1
            || cachedBorders[cachedBorders.length - 1] != totalSpace) {
        cachedBorders = new int[spanCount + 1];//比itemCount多一个元素
    }
    cachedBorders[0] = 0;//第0项为0
    int sizePerSpan = totalSpace / spanCount;//每个item占用的宽度
    int sizePerSpanRemainder = totalSpace % spanCount;//不足一个item剩下的间隙
    int consumedPixels = 0;
    int additionalSize = 0;
    for (int i = 1; i <= spanCount; i++) {
        int itemSize = sizePerSpan;
        additionalSize += sizePerSpanRemainder;
        if (additionalSize > 0 && (spanCount - additionalSize) < sizePerSpanRemainder) {//处理剩余间隙
            itemSize += 1;
            additionalSize -= spanCount;
        }
        consumedPixels += itemSize;
        cachedBorders[i] = consumedPixels;//赋值mCachedBorders
    }
    return cachedBorders;
}

步骤7 其中方法参数left top等属性都是grid item的最大坐标,如果设置了margin和Decoration 则需做对应的偏移

    public void layoutDecoratedWithMargins(@NonNull View child, int left, int top, int right,
            int bottom) {
        final LayoutParams lp = (LayoutParams) child.getLayoutParams();
        final Rect insets = lp.mDecorInsets;
        child.layout(left + insets.left + lp.leftMargin, top + insets.top + lp.topMargin,
                right - insets.right - lp.rightMargin,
                bottom - insets.bottom - lp.bottomMargin);
    }

结语

记录下学习记录,下次学vlayout的相关layoutManager相关源码

你可能感兴趣的:(android开发点滴,Android,recyclerview,layoutmanager)