备注
虽然工作有几年了,但是玩安卓的时间比较短,第一次看recycleview源码看的也是有点头大。这篇文章纯单是自己的一个学习笔记看待,描述有问题的地方,敬请谅解。
原因
为什么会有想法去看RecycleView源码,主要的原因是:刚好在工作中碰到一个问题是:在滑动item中,未配置的imageview会显示之前的配置的imageView。由于一开始对recycleview的不熟悉,这个问题弄了好久。因此,想着要不干脆一不做二不休,去理解梳理一遍源码吧。
带着问题
看源码,经常看着看着就想理解每一步的具体的逻辑,但是这样势必造成理解起来很困难,花的时间也很长。还是需要带着问题去看源码,通过打断点的方式,去查看调用方式,理清一个脉络,去慢慢的理解源码。
那么接下来,我们就慢慢去查看源码
路口
那么我们从哪里开始看代码呢?我们在一开始设置adapter调用的函数是:setAdapter
函数,那么就从这里开始看起:
/**
* Set a new adapter to provide child views on demand.
* <p>
* When adapter is changed, all existing views are recycled back to the pool. If the pool has
* only one adapter, it will be cleared.
*/
public void setAdapter(@Nullable Adapter adapter) {
....
//设置新的adapter,并且触发监听
setAdapterInternal(adapter, false, true);
processDataSetCompletelyChanged(false);
//请求布局,直接调用View类的请求布局方法
requestLayout();
}
setAdapter
函数里,按我们所关心的具体做了两件事情:
- 调用
setAdapterInternal
函数,用新的adapter替换掉老的adapter,并且触发监听。看看setAdapterInternal
具体做了什么事情:
/**
* Replaces the current adapter with the new one and triggers listeners.
* @param adapter The new adapter
* @param compatibleWithPrevious If true, the new adapter is using the same View Holders and
* item types with the current adapter (helps us avoid cache
* invalidation).
* @param removeAndRecycleViews If true, we'll remove and recycle all existing views. If
* compatibleWithPrevious is false, this parameter is ignored.
*/
private void setAdapterInternal(@Nullable Adapter adapter, boolean compatibleWithPrevious,
boolean removeAndRecycleViews) {
if (mAdapter != null) {
mAdapter.unregisterAdapterDataObserver(mObserver); // 注销老的adapter观察者
mAdapter.onDetachedFromRecyclerView(this); //解绑recycleview
}
....
mAdapterHelper.reset();
final Adapter oldAdapter = mAdapter;
mAdapter = adapter;
if (adapter != null) {
adapter.registerAdapterDataObserver(mObserver); //注册观察者
adapter.onAttachedToRecyclerView(this);
}
...
}
setAdapterInternal
函数中主要做的是注销之前注册的adapter,重新注册新的adapter,并且绑定recycleView。
- 调用
requestLayout
函数,请求布局。
我们看看requestLayout
中函数的实现:
@Override
public void requestLayout() {
if (mInterceptRequestLayoutDepth == 0 && !mLayoutSuppressed) {
super.requestLayout();
} else {
mLayoutWasDefered = true;
}
}
我们看到requestLayout
最终调用父类的requestLayout()
;但是,我现在对自定义的View不是很熟悉,按照参考的一些博客的说法是:最终调用到onLayout
函数,最终由子类自己实现onLayout
函数实现。
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
TraceCompat.beginSection(TRACE_ON_LAYOUT_TAG);
dispatchLayout();
TraceCompat.endSection();
mFirstLayoutComplete = true;
}
从这个函数我们看到,最终是调用dispatchLayout()去分发Layout。
又可以继续看看dispatchLayout
函数具体做了啥:
void dispatchLayout() {
...
if (mState.mLayoutStep == State.STEP_START) {
//dispath第一步
dispatchLayoutStep1();
mLayout.setExactMeasureSpecsFrom(this);
//dispatch第二步
dispatchLayoutStep2();
}
...
//dispatch第二步
dispatchLayoutStep3();
}
2.1 dispatch 第一步
/**
* The first step of a layout where we;
* - process adapter updates
* - decide which animation should run
* - save information about current views
* - If necessary, run predictive layout and save its information
*/
private void dispatchLayoutStep1() {
fillRemainingScrollValues(mState);
mState.mIsMeasuring = false;
startInterceptRequestLayout();
mViewInfoStore.clear();
onEnterLayoutOrScroll();
processAdapterUpdatesAndSetAnimationFlags();
saveFocusInfo();
mState.mTrackOldChangeHolders = mState.mRunSimpleAnimations && mItemsChanged;
mItemsAddedOrRemoved = mItemsChanged = false;
mState.mInPreLayout = mState.mRunPredictiveAnimations;
mState.mItemCount = mAdapter.getItemCount();
findMinMaxChildLayoutPositions(mMinMaxLayoutPositions);
if (mState.mRunSimpleAnimations) {
// Step 0: Find out where all non-removed items are, pre-layout
int count = mChildHelper.getChildCount();
for (int i = 0; i < count; ++i) {
final ViewHolder holder = getChildViewHolderInt(mChildHelper.getChildAt(i));
if (holder.shouldIgnore() || (holder.isInvalid() && !mAdapter.hasStableIds())) {
continue;
}
final ItemHolderInfo animationInfo = mItemAnimator
.recordPreLayoutInformation(mState, holder,
ItemAnimator.buildAdapterChangeFlagsForAnimations(holder),
holder.getUnmodifiedPayloads());
mViewInfoStore.addToPreLayout(holder, animationInfo);
if (mState.mTrackOldChangeHolders && holder.isUpdated() && !holder.isRemoved()
&& !holder.shouldIgnore() && !holder.isInvalid()) {
long key = getChangedHolderKey(holder);
mViewInfoStore.addToOldChangeHolders(key, holder);
}
}
}
......
mState.mLayoutStep = State.STEP_LAYOUT;
}
按照dispatchLayoutStep1()
函数说明执行以下几件事:
1.处理Adapter的更新
2.决定那些动画需要执行
3.保存当前View的信息
4.如果必要的话,执行上一个Layout的操作并且保存他的信息
5.更新mLayoutStep 为State.STEP_LAYOUT
2.2 dispatch 第二步
/**
* The second layout step where we do the actual layout of the views for the final state.
* This step might be run multiple times if necessary (e.g. measure).
*/
private void dispatchLayoutStep2() {
......
//更新mItemCount
mState.mItemCount = mAdapter.getItemCount();
mState.mDeletedInvisibleItemCountSincePreviousLayout = 0;
// Step 2: Run layout
mState.mInPreLayout = false;
//运行layout
mLayout.onLayoutChildren(mRecycler, mState);
mState.mStructureChanged = false;
mPendingSavedState = null;
......
}
朋友们,看到这一步,最重点的要来了。mLayout.onLayoutChildren
这个函数就是我们要开始规划item怎么规划,怎么布局的关键啦。这里我们放在后面介绍,这里的内容会比较多。
2.3 dispatch 第三步
The final step of the layout where we save the information about views for animations,
* trigger animations and do any necessary cleanup.
第三步按照函数的说明:主要做了保存一些views和animations信息,并且触发animations并且做一些一些必要的清理。
看到这里我们大概算是理清了一个脉络:直接拿取另外一个博主的截图:
image.png
重点来了
上面我们将一个流程梳理完成,从setAdapter
到最终的
onLayoutChildren
函数。接着我们看onLayoutChildren
具体是怎么实现的。
在dispatchLayoutStep2
函数调用的onLayoutChildren
的方式是:mLayout.onLayoutChildren(mRecycler, mState);
。而mLayout 是什么时候赋值的呢?
我们一开始在setAdapter
的时候,也会使用setLayoutManager
函数设定mLayout。这次我们以LinearLayoutManager
作为参考,进行代码解析。
因此onLayoutChildren 最终是调用mLayout对象的onLayoutChildren。这里也间接说明RecyclerView布局就通过这个mLayout布局管理者来做。
我们继续看代码:
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 (mPendingSavedState != null && mPendingSavedState.hasValidAnchor()) {
mPendingScrollPosition = mPendingSavedState.mAnchorPosition;
}
ensureLayoutState();
mLayoutState.mRecycle = false;
// resolve layout direction
resolveShouldLayoutReverse();
final View focused = getFocusedChild();
if (!mAnchorInfo.mValid || mPendingScrollPosition != RecyclerView.NO_POSITION
|| mPendingSavedState != null) {
mAnchorInfo.reset();
mAnchorInfo.mLayoutFromEnd = mShouldReverseLayout ^ mStackFromEnd;
// calculate anchor position and coordinate
updateAnchorInfoForLayout(recycler, state, mAnchorInfo);
mAnchorInfo.mValid = true;
} else if (focused != null && (mOrientationHelper.getDecoratedStart(focused)
>= mOrientationHelper.getEndAfterPadding()
|| mOrientationHelper.getDecoratedEnd(focused)
<= mOrientationHelper.getStartAfterPadding())) {
// This case relates to when the anchor child is the focused view and due to layout
// shrinking the focused view fell outside the viewport, e.g. when soft keyboard shows
// up after tapping an EditText which shrinks RV causing the focused view (The tapped
// EditText which is the anchor child) to get kicked out of the screen. Will update the
// anchor coordinate in order to make sure that the focused view is laid out. Otherwise,
// the available space in layoutState will be calculated as negative preventing the
// focused view from being laid out in fill.
// Note that we won't update the anchor position between layout passes (refer to
// TestResizingRelayoutWithAutoMeasure), which happens if we were to call
// updateAnchorInfoForLayout for an anchor that's not the focused view (e.g. a reference
// child which can change between layout passes).
mAnchorInfo.assignFromViewAndKeepVisibleRect(focused, getPosition(focused));
}
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.
mLayoutState.mLayoutDirection = mLayoutState.mLastScrollDelta >= 0
? LayoutState.LAYOUT_END : LayoutState.LAYOUT_START;
mReusableIntPair[0] = 0;
mReusableIntPair[1] = 0;
calculateExtraLayoutSpace(state, mReusableIntPair);
int extraForStart = Math.max(0, mReusableIntPair[0])
+ mOrientationHelper.getStartAfterPadding();
int extraForEnd = Math.max(0, mReusableIntPair[1])
+ mOrientationHelper.getEndPadding();
if (state.isPreLayout() && mPendingScrollPosition != RecyclerView.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) {
current = mOrientationHelper.getEndAfterPadding()
- mOrientationHelper.getDecoratedEnd(existing);
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();
// noRecycleSpace not needed: recycling doesn't happen in below's fill
// invocations because mScrollingOffset is set to SCROLLING_OFFSET_NaN
mLayoutState.mNoRecycleSpace = 0;
if (mAnchorInfo.mLayoutFromEnd) {
// fill towards start
updateLayoutStateToFillStart(mAnchorInfo);
mLayoutState.mExtraFillSpace = 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.mExtraFillSpace = 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.mExtraFillSpace = extraForStart;
fill(recycler, mLayoutState, state, false);
startOffset = mLayoutState.mOffset;
}
} else {
// fill towards end
updateLayoutSpublic 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
....
// resolve layout direction
//判断绘制方向,给mShouldReverseLayout赋值,默认是正向绘制,则mShouldReverseLayout是false
resolveShouldLayoutReverse();
final View focused = getFocusedChild();
//mValid的默认值是false,一次测量之后设为true,onLayout完成后会回调执行reset方法,又变为false
if (!mAnchorInfo.mValid || mPendingScrollPosition != NO_POSITION
|| mPendingSavedState != null) {
....
//mStackFromEnd默认是false,除非手动调用setStackFromEnd()方法,两个都会false,异或则为false
mAnchorInfo.mLayoutFromEnd = mShouldReverseLayout ^ mStackFromEnd;
// calculate anchor position and coordinate
//计算锚点的位置和偏移量
updateAnchorInfoForLayout(recycler, state, mAnchorInfo);
....
} else if (focused != null && (mOrientationHelper.getDecoratedStart(focused)
>= mOrientationHelper.getEndAfterPadding()
|| mOrientationHelper.getDecoratedEnd(focused)
<= mOrientationHelper.getStartAfterPadding())) {
....
}
....
//mLayoutFromEnd为false
if (mAnchorInfo.mLayoutFromEnd) {
//倒着绘制的话,先往上绘制,再往下绘制
// fill towards start
// 从锚点到往上
updateLayoutStateToFillStart(mAnchorInfo);
....
fill(recycler, mLayoutState, state, false);
....
// 从锚点到往下
// fill towards end
updateLayoutStateToFillEnd(mAnchorInfo);
....
//调两遍fill方法
fill(recycler, mLayoutState, state, false);
....
if (mLayoutState.mAvailable > 0) {
// end could not consume all. add more items towards start
....
updateLayoutStateToFillStart(firstElement, startOffset);
mLayoutState.mExtra = extraForStart;
fill(recycler, mLayoutState, state, false);
....
}
} else {
//正常绘制流程的话,先往下绘制,再往上绘制
// fill towards end
updateLayoutStateToFillEnd(mAnchorInfo);
....
fill(recycler, mLayoutState, state, false);
....
// fill towards start
updateLayoutStateToFillStart(mAnchorInfo);
....
fill(recycler, mLayoutState, state, false);
....
if (mLayoutState.mAvailable > 0) {
....
// start could not consume all it should. add more items towards end
updateLayoutStateToFillEnd(lastElement, endOffset);
....
fill(recycler, mLayoutState, state, false);
....
}
}
....
layoutForPredictiveAnimations(recycler, state, startOffset, endOffset);
//完成后重置参数
if (!state.isPreLayout()) {
mOrientationHelper.onLayoutComplete();
} else {
mAnchorInfo.reset();
}
mLastStackFromEnd = mStackFromEnd;
}
这里我们大概概括一下onLayoutChildren
中布局的逻辑:
- 通过
resolveShouldLayoutReverse
判断绘制的方向 - 获取锚点mAnchorInfo。
- 绘制的方式有两种:
3.1 以锚点为基准,先往上绘制,再往下绘制。
3.2 以锚点为基准,先往下绘制,再往上绘制。 - 绘制完后还有剩余空间可以绘制,继续绘制。
接下来,我们基于以上几个点继续看源码:
绘制方向
// resolve layout direction
resolveShouldLayoutReverse();
/**
* Calculates the view layout order. (e.g. from end to start or start to end)
* RTL layout support is applied automatically. So if layout is RTL and
* {@link #getReverseLayout()} is {@code true}, elements will be laid out starting from left.
*/
private void resolveShouldLayoutReverse() {
// A == B is the same result, but we rather keep it readable
if (mOrientation == VERTICAL || !isLayoutRTL()) {
mShouldReverseLayout = mReverseLayout;
} else {
mShouldReverseLayout = !mReverseLayout;
}
}
在onLayoutChildren
函数中调用resolveShouldLayoutReverse函数根据mOrientation 变量判断是横向填充还是竖向填充。mOrientation 默认是为VERTICAL,调用setOrientation
设置mOrientation。
寻找锚点
mAnchorInfo.reset();
mAnchorInfo.mLayoutFromEnd = mShouldReverseLayout ^ mStackFromEnd;
// calculate anchor position and coordinate
updateAnchorInfoForLayout(recycler, state, mAnchorInfo);
mAnchorInfo.mValid = true;
mAnchorInfo就是我们要找的锚点,它的成员变量主要有以下四个
int mPosition; 位置
int mCoordinate; 坐标
boolean mLayoutFromEnd;
boolean mValid;
mValid
默认值为false,一次测量之后设为true,onLayout完成后会回调执行reset方法,又变为false。
mLayoutFromEnd
mShouldReverseLayout默认是fasle的,mStackFromEnd默认是false,除非手动调用setStackFromEnd()方法,两个都会false,异或则为false。
updateAnchorInfoForLayout
这个函数是更新mAnchorInfo 数据中的mPosition
和mCoordinate
,后续再分析。
开始绘制
绘制的地方,主要就是两种方向,正向(先向上再向下),逆向(先向下再向上),所以这里我们就看平常的情况。
// fill towards end
updateLayoutStateToFillEnd(mAnchorInfo);
......
fill(recycler, mLayoutState, state, false);
......
// fill towards start
updateLayoutStateToFillStart(mAnchorInfo);
......
fill(recycler, mLayoutState, state, false);
......
if (mLayoutState.mAvailable > 0) {
......
updateLayoutStateToFillEnd(lastElement, endOffset);
mLayoutState.mExtraFillSpace = extraForEnd;
fill(recycler, mLayoutState, state, false);
endOffset = mLayoutState.mOffset;
}
这里我们发现主要通过updateLayoutStateToFillEnd
,updateLayoutStateToFillStart
,fill
函数进行绘制。
updateLayoutStateToFill...
其实就是确定当前方向上锚点的相关的状态信息。
fill()
主要用来绘制可以看到这里至少调用了两次fill()方法,当还有剩余可以绘制的时候会再调一次fill()方法。
这里继续引用一个博主的图:
int fill(RecyclerView.Recycler recycler, LayoutState layoutState,
RecyclerView.State state, boolean stopOnFocusable) {
.....
layoutChunk(recycler, state, layoutState, layoutChunkResult);
.....
return start - layoutState.mAvailable;
}
fill函数最终调用layoutChunk函数。
View view = layoutState.next(recycler);
.....
RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) view.getLayoutParams();
if (layoutState.mScrapList == null) {
if (mShouldReverseLayout == (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 (mOrientation == 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 {
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.
layoutDecoratedWithMargins(view, left, top, right, bottom);
....
这个函数关注点会很多,我一个个的看:
- layoutState.next(recycler);
if (mScrapList != null) {
return nextViewFromScrapList();
}
final View view = recycler.getViewForPosition(mCurrentPosition);
mCurrentPosition += mItemDirection;
return view;
按照函数说明就是用来获取view的,那么怎么获取呢?
mScrapList 这个变量默认是null,只有执行layoutForPredictiveAnimations前不为空,执行完后又变为空,所以这里暂时不需要考虑。
getViewForPosition 涉及的东西比较多,流程上简单的看一下View view = recycler.getViewForPosition(mCurrentPosition)
;
public View getViewForPosition(int position) {
return getViewForPosition(position, false);
}
View getViewForPosition(int position, boolean dryRun) {
return tryGetViewHolderForPositionByDeadline(position, dryRun, FOREVER_NS).itemView;
}
/**
* Attempts to get the ViewHolder for the given position, either from the Recycler scrap,
* cache, the RecycledViewPool, or creating it directly.
*/
/**
* 注释写的很清楚,从Recycler的scrap,cache,RecyclerViewPool,或者直接create创建
*/
@Nullable
ViewHolder tryGetViewHolderForPositionByDeadline(int position,
boolean dryRun, long deadlineNs) {
//一堆判断之后,如果不成立
holder = mAdapter.createViewHolder(RecyclerView.this, type);
}
可以看到这里,getViewForPosition会调用tryGetViewHolderForPositionByDeadline方法,tryGetViewHolderForPositionByDeadline方法的注释写的很清楚从Recycler的scrap,cache,RecyclerViewPool,或者直接create创建。
- addView
LayoutParams params = (LayoutParams) view.getLayoutParams();
if (layoutState.mScrapList == null) {
if (mShouldReverseLayout == (layoutState.mLayoutDirection
== LayoutState.LAYOUT_START)) {
addView(view);
} else {
addView(view, 0);
}
} else {
if (mShouldReverseLayout == (layoutState.mLayoutDirection
== LayoutState.LAYOUT_START)) {
addDisappearingView(view);
} else {
addDisappearingView(view, 0);
}
}
剩下的就是RecyclerView的addView方法。添加完View后会调用
//测量ChildView
measureChildWithMargins(view, 0, 0);
//----------------------------------------------------------
public void measureChildWithMargins(View child, int widthUsed, int heightUsed) {
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
//设置分割线中的回调方法
final Rect insets = mRecyclerView.getItemDecorInsetsForChild(child);
widthUsed += insets.left + insets.right;
heightUsed += insets.top + insets.bottom;
final int widthSpec = getChildMeasureSpec(getWidth(), getWidthMode(),
getPaddingLeft() + getPaddingRight()
+ lp.leftMargin + lp.rightMargin + widthUsed, lp.width,
canScrollHorizontally());
final int heightSpec = getChildMeasureSpec(getHeight(), getHeightMode(),
getPaddingTop() + getPaddingBottom()
+ lp.topMargin + lp.bottomMargin + heightUsed, lp.height,
canScrollVertically());
if (shouldMeasureChild(child, widthSpec, heightSpec, lp)) {
//子View的测量
child.measure(widthSpec, heightSpec);
}
}
从这个方法里我们看到了子View的测量,当然还有一个需要我们注意的地方那就是mRecyclerView.getItemDecorInsetsForChild(child)
Rect getItemDecorInsetsForChild(View child) {
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
if (!lp.mInsetsDirty) {
return lp.mDecorInsets;
}
if (mState.isPreLayout() && (lp.isItemChanged() || lp.isViewInvalid())) {
// changed/invalid items should not be updated until they are rebound.
return lp.mDecorInsets;
}
final Rect insets = lp.mDecorInsets;
insets.set(0, 0, 0, 0);
final int decorCount = mItemDecorations.size();
for (int i = 0; i < decorCount; i++) {
mTempRect.set(0, 0, 0, 0);
//getItemOffsets()实现分割线的回调方法!
mItemDecorations.get(i).getItemOffsets(mTempRect, child, this, mState);
insets.left += mTempRect.left;
insets.top += mTempRect.top;
insets.right += mTempRect.right;
insets.bottom += mTempRect.bottom;
}
lp.mInsetsDirty = false;
return insets;
}
其实可以看到这里在测量子View的时候是将我们实现自定义分割线重写的getItemOffsets方法。这里其实也就可以理解了自定义分割线的原理就是在子View的测量过程前给上下左右加上自定义分割线所对应设置给这个child的边距。
测量完成后,紧接着就调用了layoutDecoratedWithMargins(view, left, top, right, bottom)对子View完成了layout。
image.png
public void layoutDecoratedWithMargins(View child, int left, int top, int right,
int bottom) {
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
final Rect insets = lp.mDecorInsets;
//layout
child.layout(left + insets.left + lp.leftMargin, top + insets.top + lp.topMargin,
right - insets.right - lp.rightMargin,
bottom - insets.bottom - lp.bottomMargin);
}
最后
中间有大量摘录
博客:https://www.jianshu.com/p/c52b947fe064
博客:https://www.jianshu.com/p/10298503c134
这篇文章全当自己的学习笔记。
网友评论