项目有用到阿里的tangram3动态布局框架,有时候某些特殊需求想定制的时候会比较头疼,其中这个框架又依赖vlayout,所以你都要了解内部原理,最近看到vlayout的layoutManager相关代码,想着之前只看过LinearLayoutManager的布局流程 但是还没看过GridLayoutManager的,所以就有了这篇学习记录
GridLayoutManager是继承于LinearLayoutManager
工作流程大概是:
RecyclerView.onLayout -> RecyclerView.dispatchLayout -> LinearLayoutManager.onLayoutChildren -> LinearLayoutManager.fill -> LinearLayoutManager.layoutChunk
GridLayoutManager最重要的一个方法就是layoutChunk,它主要是负责添加view和设置view的实际位置
为了方便理解我们设置以下条件:
排除干扰代码后伪代码如下,分七步,大概了解下下面代码就行,下面拆分代码,记录这次学习记录
@Override
void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state,LayoutState layoutState, LayoutChunkResult result) {final int otherDirSpecMode = mOrientationHelper.getModeInOther();//默认EXACTLYfinal boolean layingOutInPrimaryDirection =layoutState.mItemDirection == LayoutState.ITEM_DIRECTION_TAIL;//是否按顺序添加也就是 item排列方式为123456int count = 0;int remainingSpan = mSpanCount;//列数//1while (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;//当前行最大高度//2assignSpans(recycler, state, count, layingOutInPrimaryDirection);//3for (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都为0measureChild(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;//5if (mOrientation == VERTICAL) {if (layoutState.mLayoutDirection == LayoutState.LAYOUT_START) {//layoutState.mLayoutDirection是由锚点方向决定 一般都是LAYOUT_ENDbottom = layoutState.mOffset;top = bottom - maxSize;} else {top = layoutState.mOffset;//mOffset为layoutManage上一次填充后的结束点bottom = top + maxSize;}}//6for (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);
}
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();//LinearLayoutManagervoid 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;//默认设置}
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;//设置真实indexspan += params.mSpanSize;}
}
往RecycleView中添加view
获取view中的Decoration(间隙)添加进入mDecorInsets(Rect)中(好像全局变量没用到,用到都是LayoutParams.mDecorInsets)
测量子view的宽高
获取到这一行最大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都为0measureChild(view, otherDirSpecMode, false);//测量子view宽高final int size = mOrientationHelper.getDecoratedMeasurement(view);//获取view的垂直方向大小,也就是高度if (size > maxSize) {//maxSize初始为0 这时候赋值maxSize = size;}}
//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的上下Marginfinal 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;
if (mOrientation == VERTICAL) {//1if (layoutState.mLayoutDirection == LayoutState.LAYOUT_START) {//layoutState.mLayoutDirection是由锚点方向决定 初始化首屏是LAYOUT_ENDbottom = layoutState.mOffset;top = bottom - maxSize;} else {//2top = layoutState.mOffset;//mOffset为layoutManage上一次填充后的结束点bottom = top + maxSize;}}
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项为0int 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;
}
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相关源码
上一篇:Activiti学习01