類名 | 功能 |
---|---|
RecyclerView.LayoutManager | 負責Item視圖的佈局的顯示 |
RecyclerView.ItemDecoration | 繪製Item的分割樣式 |
RecyclerView.ItemAnimator | 負責處理數據添加刪除時的動畫效果 |
RecyclerView.Adapter | 創建每一項Item視圖 |
RecyclerView.ViewHolder | 承載Item視圖的子佈局 |
RecyclerView.Recycler | 處理View的緩存 |
RecyclerView 把子 View 的 measure 和 layout 委託給了 LayoutManager,我們可以通過自定義 LayoutManager 來自定義 RecyclerView 的子 View 排列規則。Google 官方給我提供了3個實現類,LinearLayoutManager、GridLayoutManager、StaggeredGridLayoutManager.
下面我們根據源碼一步步來看 RecyclerView 是怎麼實現的。
1、setLayoutManager(RecyclerView.LayoutManager layout):
設置LayoutManager的方法
public void setLayoutManager(@Nullable RecyclerView.LayoutManager layout) {
if (layout != this.mLayout) {
//停止滾動
this.stopScroll();
if (this.mLayout != null) {
if (this.mItemAnimator != null) {
//結束動畫
this.mItemAnimator.endAnimations();
}
//移除並回收視圖
this.mLayout.removeAndRecycleAllViews(this.mRecycler);
//回收廢棄視圖
this.mLayout.removeAndRecycleScrapInt(this.mRecycler);
this.mRecycler.clear();
if (this.mIsAttached) {
this.mLayout.dispatchDetachedFromWindow(this, this.mRecycler);
}
this.mLayout.setRecyclerView((RecyclerView)null);
this.mLayout = null;
} else {
this.mRecycler.clear();
}
this.mChildHelper.removeAllViewsUnfiltered();
//更新layout
this.mLayout = layout;
...
//更新緩存的View數量
this.mRecycler.updateViewCacheSize();
//請求measure layout draw
this.requestLayout();
}
}
2、setAdapter(RecyclerView.Adapter adapter)\
setAdapterInternal(RecyclerView.Adapter adapter, boolean compatibleWithPrevious, boolean removeAndRecycleViews):
設置Adapter的方法
public void setAdapter(@Nullable RecyclerView.Adapter adapter) {
this.setLayoutFrozen(false);
this.setAdapterInternal(adapter, false, true);
this.processDataSetCompletelyChanged(false);
this.requestLayout();
}
private void setAdapterInternal(@Nullable RecyclerView.Adapter adapter, boolean compatibleWithPrevious, boolean removeAndRecycleViews) {
...
this.mAdapter = adapter;
...
}
setAdapter()方法中調用了setAdapterInternal()方法,在setAdapterInternal()方法中初始化,然後調用 requestLayout()方法。
public void requestLayout() {
if (this.mInterceptRequestLayoutDepth == 0 && !this.mLayoutFrozen) {
super.requestLayout();
} else {
this.mLayoutWasDefered = true;
}
}
requestLayout()方法其實調用的是View的requestLayout()方法。
public void requestLayout() {
...
if (mParent != null && !mParent.isLayoutRequested()) {
mParent.requestLayout();
}
...
}
其中mParent.requestLayout()方法調用的是ViewRootImpl的requestLayout()方法
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
scheduleTraversals();
}
}
scheduleTraversals()方法被執行之後會走RecyclerView的onMeasure()、onLayout()、onDraw()等繪製方法。
3、onMeasure()、onLayout()
先來看一下onMeasure()方法的源碼:
protected void onMeasure(int widthSpec, int heightSpec) {
...
if (mLayout.mAutoMeasure) {
final int widthMode = MeasureSpec.getMode(widthSpec);
final int heightMode = MeasureSpec.getMode(heightSpec);
//當 RecyclerView 當 Mode 是 EXACTLY 的時候大小是確定的,所以 return 退出 measure
final boolean skipMeasure = widthMode == MeasureSpec.EXACTLY
&& heightMode == MeasureSpec.EXACTLY;
mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);
if (skipMeasure || mAdapter == null) {
return;
}
if (mState.mLayoutStep == State.STEP_START) {
//記錄 layout 之前,view 的信息
dispatchLayoutStep1();
}
// set dimensions in 2nd step. Pre-layout should happen with old dimensions for
// consistency
mLayout.setMeasureSpecs(widthSpec, heightSpec);
mState.mIsMeasuring = true;
dispatchLayoutStep2();
// now we can get the width and height from the children.
mLayout.setMeasuredDimensionFromChildren(widthSpec, heightSpec);
// if RecyclerView has non-exact width and height and if there is at least one child
// which also has non-exact width & height, we have to re-measure.
if (mLayout.shouldMeasureTwice()) {
mLayout.setMeasureSpecs(
MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(getMeasuredHeight(), MeasureSpec.EXACTLY));
mState.mIsMeasuring = true;
dispatchLayoutStep2();
// now we can get the width and height from the children.
mLayout.setMeasuredDimensionFromChildren(widthSpec, heightSpec);
}
} else {
...
}
}
}
mAutoMeasure的值是用來標記是否自動測量的,否則就必須在 LayoutManager 中自己實現 onMeasure 來進行測量,其中Google提共的三個LayoutManager實現類的mAutoMeasure返回值都是true。
public boolean isAutoMeasureEnabled() {
return true;
}
所以我們看 else 中的代碼就可以了,首先如果RecyclerView 的 Mode 是 EXACTLY 的時候大小是確定的,所以就 return 退出 measure。然後會調用dispatchLayoutStep1()方法,它的作用我們看一下注釋:
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
佈局的第一步,進程適配器更新,決定應運行哪個動畫,保存有關當前 View 的信息,如果有必要,運行 predictive layout。然後調用了dispatchLayoutStep2()方法。
/**
* 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() {
...
this.mLayout.onLayoutChildren(this.mRecycler, this.mState);
...
mState.mLayoutStep = State.STEP_ANIMATIONS;
...
}
其中都核心代碼就是調用了mLayout的onLayoutChildren()方法,這裏的 mLayout 就是RecyclerView.LayourManager對象,用來規定放置子 view 的算法,尋找錨點填充 view。所以到這裏我們可以知道RecyclerView的measure過程委託給了RecyclerView.LayourManager的onLayoutChildren()方法來處理。
再看看onLayout()方法:
protected void onLayout(boolean changed, int l, int t, int r, int b) {
...
this.dispatchLayout();
...
}
其中 dispatchLayout() 方法的源碼如下:
void dispatchLayout() {
...
this.mState.mIsMeasuring = false;
if (this.mState.mLayoutStep == State.STEP_START) {
this.dispatchLayoutStep1();
this.mLayout.setExactMeasureSpecsFrom(this);
this.dispatchLayoutStep2();
} else if (!this.mAdapterHelper.hasUpdates() && this.mLayout.getWidth() == this.getWidth() && this.mLayout.getHeight() == this.getHeight()) {
this.mLayout.setExactMeasureSpecsFrom(this);
} else {
this.mLayout.setExactMeasureSpecsFrom(this);
this.dispatchLayoutStep2();
}
//保存 view 動畫的信息,執行動畫,狀態清理。
this.dispatchLayoutStep3();
}
dispatchLayout()裏面調用了 dispatchLayoutStep1()、dispatchLayoutStep2()、dispatchLayoutStep3()方法,前兩個方法的作用我們之前已經說過,現在來看看 dispatchLayoutStep3() 方法,源碼太長看一下注釋:
/**
* The final step of the layout where we save the information about views for animations,
* trigger animations and do any necessary cleanup.
*/
private void dispatchLayoutStep3() {
...
mState.mLayoutStep = State.STEP_START;
...
}
所以它的功能是保存 view 動畫的信息,執行動畫,狀態清理。
這裏有個重要點就是狀態類 State,它是標記 layout 過程進行到哪一步了。在onMeasure()中當RecyclerView當Mode不是EXACTLY的時候,會執行dispatchLayoutStep1()、dispatchLayoutStep2()方法來測量子View的寬高來確定 RecyclerView自己的寬高,在onlayout()中就不需要執行了。當RecyclerView當Mode是EXACTLY的時候的時候不會執行dispatchLayoutStep1()、dispatchLayoutStep2()方法,在onlayout()中就需要執行dispatchLayoutStep1()、dispatchLayoutStep2()方法。
所以 RecyclerView的layout過程也委託給了RecyclerView.LayourManager的onLayoutChildren()方法來處理。
那麼下面以LinearLayoutmanager爲例:
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
...
final View focused = getFocusedChild();
if (!mAnchorInfo.mValid || mPendingScrollPosition != NO_POSITION
|| mPendingSavedState != null) {
mAnchorInfo.reset();
mAnchorInfo.mLayoutFromEnd = mShouldReverseLayout ^ mStackFromEnd;
updateAnchorInfoForLayout(recycler, state, mAnchorInfo);
mAnchorInfo.mValid = true;
} else if (focused != null && (mOrientationHelper.getDecoratedStart(focused)
>= mOrientationHelper.getEndAfterPadding()
|| mOrientationHelper.getDecoratedEnd(focused)
<= mOrientationHelper.getStartAfterPadding())) {
mAnchorInfo.assignFromViewAndKeepVisibleRect(focused, getPosition(focused));
}
...
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;
}
}
...
}
根據方法中的註釋可以知道,首先確定錨點mAnchorInfo,mAnchorInfo包含了子控件在Y軸上起始繪製偏移量(coordinate),ItemView在Adapter中的索引位置(position)和佈局方向(mLayoutFromEnd)——這裏是指start、end方向
static class AnchorInfo {
OrientationHelper mOrientationHelper;
int mPosition;
int mCoordinate;
boolean mLayoutFromEnd;
boolean mValid;
...
}
然後以錨點爲起點向着開始和結束的方向填充ItemView。其中真正的填充方法是 fill() 方法:
/**
* The magic functions :). Fills the given layout, defined by the layoutState. This is fairly
* independent from the rest of the {@link android.support.v7.widget.LinearLayoutManager}
* and with little change, can be made publicly available as a helper class.
*/
int fill(RecyclerView.Recycler recycler, LayoutState layoutState,
RecyclerView.State state, boolean stopOnFocusable) {
...
int remainingSpace = layoutState.mAvailable + layoutState.mExtra;
LayoutChunkResult layoutChunkResult = mLayoutChunkResult;
while ((layoutState.mInfinite || remainingSpace > 0) && layoutState.hasMore(state)) {
...
layoutChunk(recycler, state, layoutState, layoutChunkResult);
...
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.SCROLLING_OFFSET_NaN) {
layoutState.mScrollingOffset += layoutChunkResult.mConsumed;
if (layoutState.mAvailable < 0) {
layoutState.mScrollingOffset += layoutState.mAvailable;
}
recycleByLayoutState(recycler, layoutState);
}
...
}
...
return start - layoutState.mAvailable;
}
由上面的僞代碼可知,fill() 方法通過while循環調用 layoutChunk() 方法來填充子 View。下面看看 layoutChunk()方法:
void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state,
LayoutState layoutState, LayoutChunkResult result) {
View view = layoutState.next(recycler);
...
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);
}
}
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);
...
result.mFocusable = view.hasFocusable();
}
其中 View 通過 next()方法取出:
View next(RecyclerView.Recycler recycler) {
if (mScrapList != null) {
return nextViewFromScrapList();
}
final View view = recycler.getViewForPosition(mCurrentPosition);
mCurrentPosition += mItemDirection;
return view;
}
在 next()方法中通過 recycler.getViewForPosition(mCurrentPosition) 方法獲取的 View,這裏是RecyclerView的緩存機制來,先不說。回到上面layoutChunk()方法,取到View之後通過addView()方法添加。