添加View
添加一个View是调用layoutChunk方法来完成的,让我们来看看这个layoutChunk方法:
void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state,
LayoutState layoutState, LayoutChunkResult result) {
// 获取到view对象。
View view = layoutState.next(recycler);
if (view == null) {
if (DEBUG && layoutState.mScrapList == null) {
throw new RuntimeException("received null view when unexpected");
}
// if we are laying out views in scrap, this may return null which means there is
// no more items to layout.
// 如果从回收的view对象里面scraplist 里面获取view的话,真的有可能出现
// null 的情况,表示没有更多的item对象给你布局。添加
result.mFinished = true;
return;
}
// 从这里获取到布局的参数
LayoutParams params = (LayoutParams) view.getLayoutParams();
if (layoutState.mScrapList == null) {
// 从头上面加view 下面是从后面加view
if (mShouldReverseLayout == (layoutState.mLayoutDirection
== LayoutState.LAYOUT_START)) {
addView(view);
} else {
addView(view, 0);
}
} else { // 表示mScrapList 不为空
if (mShouldReverseLayout == (layoutState.mLayoutDirection
== LayoutState.LAYOUT_START)) {
// 有缓存从缓存中获取
addDisappearingView(view);
} else {
addDisappearingView(view, 0);
}
}
// 测量view的宽高
measureChildWithMargins(view, 0, 0);
// 这个方法找不到 这个方法我理解的是 测量它占用的位置是多少。相当于就是消费了多少距离
result.mConsumed = mOrientationHelper.getDecoratedMeasurement(view);
int left, top, right, bottom;
if (mOrientation == VERTICAL) {
// 方向垂直 布局从右边到左
// 这里是获取 上下、左右 四个方向上的位置
if (isLayoutRTL()) {
right = getWidth() - getPaddingRight();
/**
* 这个是getDecoratedMeasurementInOther的方法解释
*Returns the space occupied by this View in the perpendicular orientation including
* decorations and margins.
* 返回这个view在垂直方向方向上的占用,包括margin的距离,但是根据下面的用法
* 好像不是这个意思
*/
left = right - mOrientationHelper.getDecoratedMeasurementInOther(view);
} else {
left = getPaddingLeft();
right = left + mOrientationHelper.getDecoratedMeasurementInOther(view);
}
if (layoutState.mLayoutDirection == LayoutState.LAYOUT_START) {
// mLayoutState.mOffset 用于在添加布局的时候,根据它来确定被添加子View的布局位置。
// 就是表明在添加的时候,从哪里开始添加。
bottom = layoutState.mOffset;
// 这个滑动方向是从下往上翻,表明开始位置从下往上开始添加的
// 开始位置减去view占用的距离,就是顶部的位置。下面也是差不多的道理。
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;
}
}
// We calculate everything with View's bounding box (which includes decor and margins)
// To calculate correct layout position, we subtract margins.
// 我们计算每个装 view的大小。减掉 margin
layoutDecorated(view, left + params.leftMargin, top + params.topMargin,
right - params.rightMargin, bottom - params.bottomMargin);
if (DEBUG) {
Log.d(TAG, "laid out child at position " + getPosition(view) + ", with l:"
+ (left + params.leftMargin) + ", t:" + (top + params.topMargin) + ", r:"
+ (right - params.rightMargin) + ", b:" + (bottom - params.bottomMargin));
}
// Consume the available space if the view is not removed OR changed
// 如果到边界了没有移动啥的。吧这个距离消费掉。
if (params.isItemRemoved() || params.isItemChanged()) {
result.mIgnoreConsumed = true;
}
result.mFocusable = view.isFocusable();
}
/**
* Gets the view for the next element that we should layout.
* Also updates current item index to the next item, based on {@link #mItemDirection}
*
* @return The next element that we should layout.
* 这里是要添加到recycle view 里面的view子对象,从这里获取
*/
View next(RecyclerView.Recycler recycler) {
if (mScrapList != null) {
return nextViewFromScrapList();
}
final View view = recycler.getViewForPosition(mCurrentPosition);
mCurrentPosition += mItemDirection;
return view;
}
layoutChunk 方法执行完毕之后,继续分析。
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.SCOLLING_OFFSET_NaN) {
// TODO ugly bug fix. should not happen
if (layoutState.mAvailable < 0) {
layoutState.mScrollingOffset += layoutState.mAvailable;
}
recycleByLayoutState(recycler, layoutState);
}
int remainingSpace = layoutState.mAvailable + layoutState.mExtra;
LayoutChunkResult layoutChunkResult = new LayoutChunkResult();
/**
*
* // 这个是就是 layoutState.mInfinite的原始的定义的位置,自己不设置成无限大就没问题
* // 基本上都是false
mOrientationHelper.getMode() == View.MeasureSpec.UNSPECIFIED
&& mOrientationHelper.getEnd() == 0;
*
* layoutState.hasMore(state) 表示当前adapter里面是否还有更多的数据要显示。
*
* remainingSpace 定义
* int remainingSpace = layoutState.mAvailable + layoutState.mExtra;
* layoutState.mAvailable 解释:Number of pixels that we should fill, in the
* layout direction 就是在我们布局的方向上,有多少地方我们要去填充的,应该表示移动的距离。
*
* layoutState.mExtra 解释:Used if you want to pre-layout items that are not yet
* visible.
* The difference with {@link #mAvailable} is that, when recycling, distance
* laid out for
* {@link #mExtra} is not considered to avoid recycling visible children.
* 如果在还没有显示之前提前布局就用到这个值,不同于mAvailable的是,当正在回收的时候,
* 距离布局到mExtra 是不考虑避开正在显示的子类
* 根据下面使用的方法的意思,应该是最终可以移动的距离,与当前这次移动的距离的值,如果大于0,
* 说明这里可以移动,然后后面就是绘制控件到recycle上去。
*/
while ((layoutState.mInfinite || remainingSpace > 0) && layoutState.hasMore(state)) {
layoutChunkResult.resetInternal();
layoutChunk(recycler, state, layoutState, layoutChunkResult);
if (layoutChunkResult.mFinished) {
break;
}
layoutState.mOffset += layoutChunkResult.mConsumed * layoutState.mLayoutDirection;
/**
* Consume the available space if:
* * layoutChunk did not request to be ignored
* * OR we are laying out scrap children
* * OR we are not doing pre-layout
*/
if (!layoutChunkResult.mIgnoreConsumed || mLayoutState.mScrapList != null
|| !state.isPreLayout()) {
layoutState.mAvailable -= layoutChunkResult.mConsumed;
// we keep a separate remaining space because mAvailable is important for recycling
remainingSpace -= layoutChunkResult.mConsumed;
}
if (layoutState.mScrollingOffset != LayoutState.SCOLLING_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;
}
delta 应该是增量的意思
layoutState.mExtra 我是处女座的,我非要搞清楚这玩意是什么,在源码里面find一下,找到源头
mLayoutState.mExtra = + upcomingOffset + mOrientationHelper.getStartAfterPadding();
在垂直布局中。getTotalSpace() 就是获取整个
public int getTotalSpace() {
return mLayoutManager.getHeight() - mLayoutManager.getPaddingTop()
- mLayoutManager.getPaddingBottom();
}
public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
// layout algorithm:
// 1) by checking children and other variables, find an anchor coordinate and an anchor
// item position. 找到一个锚点
// 2) fill towards start, stacking from bottom 从底部添加
// 3) fill towards end, stacking from top 从顶部堆放开始
// 4) scroll to fulfill requirements like stack from bottom.
// create layout state 创建布局状态
if (DEBUG) {
Log.d(TAG, "is pre layout:" + state.isPreLayout());
}
if (mPendingSavedState != null || mPendingScrollPosition != NO_POSITION) {
if (state.getItemCount() == 0) {
removeAndRecycleAllViews(recycler);
return;
}
}
if (mPendingSavedState != null && mPendingSavedState.hasValidAnchor()) {
mPendingScrollPosition = mPendingSavedState.mAnchorPosition;
}
ensureLayoutState();
mLayoutState.mRecycle = false;
// resolve layout direction
resolveShouldLayoutReverse();
mAnchorInfo.reset();
mAnchorInfo.mLayoutFromEnd = mShouldReverseLayout ^ mStackFromEnd;
// calculate anchor position and coordinate
updateAnchorInfoForLayout(recycler, state, mAnchorInfo);
if (DEBUG) {
Log.d(TAG, "Anchor info:" + mAnchorInfo);
}
// LLM may decide to layout items for "extra" pixels to account for scrolling target,
// caching or predictive animations.
// 当前这个类使用layout 去记录extra的像素,就是记录滚动的目标,缓存或者预测动画。
int extraForStart;
int extraForEnd;
final int extra = getExtraLayoutSpace(state); // 这里就是屏幕显示高度减去上下margin
// If the previous scroll delta was less than zero, the extra space should be laid out
// at the start. Otherwise, it should be at the end.
// 根据滚动的值,如果小于0,从上面头开始布局,否则就从尾巴开始
if (mLayoutState.mLastScrollDelta >= 0) { // mLastScrollDelta 表示最近一次的滑动
extraForEnd = extra;
extraForStart = 0;
} else {
extraForStart = extra;
extraForEnd = 0;
}
extraForStart += mOrientationHelper.getStartAfterPadding();
extraForEnd += mOrientationHelper.getEndPadding();
if (state.isPreLayout() && mPendingScrollPosition != NO_POSITION &&
mPendingScrollPositionOffset != INVALID_OFFSET) {
// if the child is visible and we are going to move it around, we should layout
// extra items in the opposite direction to make sure new items animate nicely
// instead of just fading in
// 如果有正在显示的子类,
final View existing = findViewByPosition(mPendingScrollPosition);
if (existing != null) {
final int current;
final int upcomingOffset; // 即将移动的距离
if (mShouldReverseLayout) {
// 就是getEndAfterPadding 显示的高度,减掉最下面的padding
// 与当前的控件的底部的位置的差
current = mOrientationHelper.getEndAfterPadding() -
mOrientationHelper.getDecoratedEnd(existing);
// 就是当前的位置,减掉 mPendingScrollPositionOffset
// 就是每次offset 移动的recycle的距离 缓存下来了
upcomingOffset = current - mPendingScrollPositionOffset;
} else {
current = mOrientationHelper.getDecoratedStart(existing)
- mOrientationHelper.getStartAfterPadding();
upcomingOffset = mPendingScrollPositionOffset - current;
}
// 上面的移动
if (upcomingOffset > 0) {
extraForStart += upcomingOffset;
} else {
extraForEnd -= upcomingOffset;
}
}
}
int startOffset;
int endOffset;
final int firstLayoutDirection;
if (mAnchorInfo.mLayoutFromEnd) {
firstLayoutDirection = mShouldReverseLayout ? LayoutState.ITEM_DIRECTION_TAIL :
LayoutState.ITEM_DIRECTION_HEAD;
} else {
firstLayoutDirection = mShouldReverseLayout ? LayoutState.ITEM_DIRECTION_HEAD :
LayoutState.ITEM_DIRECTION_TAIL;
}
onAnchorReady(recycler, state, mAnchorInfo, firstLayoutDirection);
detachAndScrapAttachedViews(recycler);
mLayoutState.mInfinite = resolveIsInfinite();
mLayoutState.mIsPreLayout = state.isPreLayout();
if (mAnchorInfo.mLayoutFromEnd) {
// fill towards start
updateLayoutStateToFillStart(mAnchorInfo);
mLayoutState.mExtra = 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.mExtra = 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.mExtra = extraForStart;
fill(recycler, mLayoutState, state, false);
startOffset = mLayoutState.mOffset;
}
} else {
// fill towards end
updateLayoutStateToFillEnd(mAnchorInfo);
mLayoutState.mExtra = 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.mExtra = 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.mExtra = extraForEnd;
fill(recycler, mLayoutState, state, false);
endOffset = mLayoutState.mOffset;
}
}
// changes may cause gaps on the UI, try to fix them.
// TODO we can probably avoid this if neither stackFromEnd/reverseLayout/RTL values have
// changed
if (getChildCount() > 0) {
// because layout from end may be changed by scroll to position
// we re-calculate it.
// find which side we should check for gaps.
if (mShouldReverseLayout ^ mStackFromEnd) {
int fixOffset = fixLayoutEndGap(endOffset, recycler, state, true);
startOffset += fixOffset;
endOffset += fixOffset;
fixOffset = fixLayoutStartGap(startOffset, recycler, state, false);
startOffset += fixOffset;
endOffset += fixOffset;
} else {
int fixOffset = fixLayoutStartGap(startOffset, recycler, state, true);
startOffset += fixOffset;
endOffset += fixOffset;
fixOffset = fixLayoutEndGap(endOffset, recycler, state, false);
startOffset += fixOffset;
endOffset += fixOffset;
}
}
layoutForPredictiveAnimations(recycler, state, startOffset, endOffset);
if (!state.isPreLayout()) {
mPendingScrollPosition = NO_POSITION;
mPendingScrollPositionOffset = INVALID_OFFSET;
mOrientationHelper.onLayoutComplete();
}
mLastStackFromEnd = mStackFromEnd;
mPendingSavedState = null; // we don't need this anymore
if (DEBUG) {
validateChildOrder();
}
}
把人家的分析接下来写原文