项目有用到阿里的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();//默认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);
}
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;//默认设置
}
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;
}
}
往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都为0
measureChild(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的上下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;
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;
}
}
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;
}
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相关源码