- 首先分析一下Recyclerview最核心的部分,也就是计算子view,layout 子view,计算剩余空间,直到剩余空间为0的部分,就是dispatchLayoutStep2
private void dispatchLayoutStep2() {
......
mLayout.onLayoutChildren(mRecycler, mState);
......
}
可以看到这个部分其实是委托给了layoutmanager来完成的。
- 我们首先来看下LinearLayoutManager的onLayoutChildren.
//LinearLayoutManager 的onLayoutChildren方法
public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
......
updateAnchorInfoForLayoutExpose(state, mAnchorInfo);
......
detachAndScrapAttachedViews(recycler);
if (mAnchorInfo.mLayoutFromEnd) {
// fill towards start 和下面的逻辑分支是反过来的,仅看下面的逻辑为讲解。
......
} else {
// fill towards end
updateLayoutStateToFillEndExpose(mAnchorInfo);
mLayoutState.mExtra = extraForEnd;
fill(recycler, mLayoutState, state, false);
endOffset = mLayoutState.mOffset;
if (mLayoutState.mAvailable > 0) {
extraForStart += mLayoutState.mAvailable;
}
// fill towards start
updateLayoutStateToFillStartExpose(mAnchorInfo);
mLayoutState.mExtra = extraForStart;
mLayoutState.mCurrentPosition += mLayoutState.mItemDirection;
fill(recycler, mLayoutState, state, false);
startOffset = mLayoutState.mOffset;
}
......
}
- 首先updateAnchorInfoForLayout是用来更新锚点信息的,锚点是用以layoutmanager渲染view的坐标
- 第一步找锚点和位置确定相对坐标和 item 位置;
- 对容器上的 View 做detach 和回收处理见调用detachAndScrapAttachedViews
- 填充 View 到可用空间,以锚点为界,在布局方向以相反的两个方向扩张来填充可用空间
以描点为界向两个方向填充的算法示意图如下,其中红色点为描点位置:
- 接着我们来看fill方法,这个方法就是用view来填充剩余空间
protected int fill(RecyclerView.Recycler recycler, LayoutState layoutState,
RecyclerView.State state, boolean stopOnFocusable) {
......
if (layoutState.mScrollingOffset != LayoutState.SCOLLING_OFFSET_NaN) {
......
recycleByLayoutState(recycler, layoutState);
}
int remainingSpace = layoutState.mAvailable + layoutState.mExtra;
while (remainingSpace > 0 && layoutState.hasMore(state)) {
layoutChunkResultCache.resetInternal();
layoutChunk(recycler, state, layoutState, layoutChunkResultCache);
if (layoutChunkResultCache.mFinished) {
break;
}
layoutState.mOffset += layoutChunkResultCache.mConsumed * layoutState.mLayoutDirection;
if (!layoutChunkResultCache.mIgnoreConsumed || mLayoutState.mScrapList != null
|| !state.isPreLayout()) {
layoutState.mAvailable -= layoutChunkResultCache.mConsumed;
// we keep a separate remaining space because mAvailable is important for recycling
remainingSpace -= layoutChunkResultCache.mConsumed;
} ......
}
if (DEBUG) {
validateChildOrderExpose();
}
return start - layoutState.mAvailable;
}
- 提醒一下LayoutState.LAYOUT_START和LAYOUT_END意思是Toward Start还是Toward End,意思是LAYOUT_END是从上往下布局。
- 这个方法本质就是不断的把子view放到空余空间中,直到remainingSpace<=0,也就是空间不足了。
- 首先会通过recycleByLayoutState方法移除已经滑出屏幕以外的子视图
=======
for (int i = 0; i < childCount; i++) {
View child = getChildAt(i);
if (mOrientationHelper.getDecoratedEnd(child) > limit
|| mOrientationHelper.getTransformedEndWithDecoration(child) > limit) {
// stop here
recycleChildren(recycler, 0, i);
return;
}
}
======
- 可以看到是计算出第一个下边界大于limit的view,然后把这个view之前的view全部回收掉。
- 然后通过一个循环的朝着一个布局方向每次调用layoutChunk添加 View,并适时调节剩余空间和下次 layout 的 offset,直到剩下的空间不足循环结束。而子视图逐个的测量和布局就是在layoutChunk这个方法里进行的,看下面部分源码:
- 而真正对子view进行measure和layout的方法是layoutChunk。
protected void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state,
LayoutState layoutState, LayoutChunkResult result) {
View view = layoutState.next(recycler);
......
RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) view.getLayoutParams();
if (layoutState.mScrapList == null) {
// can not find in scrapList
if (mShouldReverseLayoutExpose == (layoutState.mLayoutDirection
== LayoutState.LAYOUT_START)) {
addView(view);
} else {
addView(view, 0);
}
}
......
measureChildWithMargins(view, 0, 0);
result.mConsumed = mOrientationHelper.getDecoratedMeasurement(view);
int left, top, right, bottom;
if (getOrientation() == 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 {
......
}
// We calculate everything with View's bounding box (which includes decor and margins)
// To calculate correct layout position, we subtract margins.
layoutDecorated(view, left + params.leftMargin, top + params.topMargin,
right - params.rightMargin, bottom - params.bottomMargin);
......
result.mFocusable = view.isFocusable();
}
-
这里面做了这几件事
- 1:measureChild measure子view的宽高
- 更新mConsumed,要放置这个view一定会消耗一部分空间,所以更新mConsumed的值
- layout 子view,根据mOffset和measure出来的子view的宽高,把子view放到该放的地方
然后就这么继续下去,知道remainSpace小于0,无法放置新的view了。
GridLayoutManager继承自LinearLayoutManager,核心的布局方法差不多
@Override
public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
if (state.isPreLayout()) {
cachePreLayoutSpanMapping();
}
super.onLayoutChildren(recycler, state);
if (DEBUG) {
validateChildOrder();
}
clearPreLayoutSpanMappingCache();
}
- 可以看到调用的是LinearLayoutManager的onLayoutChildren。
- 不一样的地方在于layoutChunk这个方法,LinearLayoutManager一行只放一个,GridLayoutManager一行会放多个,具体几个取决于他的列数。
@Override
void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state,
LayoutState layoutState, LayoutChunkResult result) {
················
while (count < mSpanCount && layoutState.hasMore(state) && remainingSpan > 0) {
int pos = layoutState.mCurrentPosition;
final int spanSize = getSpanSize(recycler, state, pos);
if (spanSize > mSpanCount) {
throw new IllegalArgumentException("Item at position " + pos + " requires " +
spanSize + " spans but GridLayoutManager has only " + mSpanCount
+ " spans.");
}
remainingSpan -= spanSize;
if (remainingSpan < 0) {
break; // item did not fit into this row or column
}
View view = layoutState.next(recycler);
if (view == null) {
break;
}
consumedSpanCount += spanSize;
mSet[count] = view;
count++;
}
································
int maxSize = 0;
float maxSizeInOther = 0; // use a float to get size per span
// we should assign spans before item decor offsets are calculated
assignSpans(recycler, state, count, consumedSpanCount, layingOutInPrimaryDirection);
for (int i = 0; i < count; i++) {
·········································
addView()
calculateItemDecorationsForChild(view, mDecorInsets);
measureChild(view, otherDirSpecMode, false);
final int size = mOrientationHelper.getDecoratedMeasurement(view);
if (size > maxSize) {
maxSize = size;
}
final LayoutParams lp = (LayoutParams) view.getLayoutParams();
final float otherSize = 1f * mOrientationHelper.getDecoratedMeasurementInOther(view) /
lp.mSpanSize;
if (otherSize > maxSizeInOther) {
maxSizeInOther = otherSize;
}
}
······························
result.mConsumed = maxSize;
int left = 0, right = 0, top = 0, bottom = 0;
if (mOrientation == VERTICAL) {
if (layoutState.mLayoutDirection == LayoutState.LAYOUT_START) {
bottom = layoutState.mOffset;
top = bottom - maxSize;
} else {
top = layoutState.mOffset;
bottom = top + maxSize;
}
} else {
if (layoutState.mLayoutDirection == LayoutState.LAYOUT_START) {
right = layoutState.mOffset;
left = right - maxSize;
} else {
left = layoutState.mOffset;
right = left + maxSize;
}
}
·······························
- 其实核心逻辑就是取一行中最高的maxSize作为mConsumed已经消耗的空间,如果view不一样高,其他矮的view会被重新计算,拉到和最高的view 一样高,所以gridlayoutmanager是一定一样高