#### **[RecyclerView](https://www.androidos.net.cn/android/5.0.1_r1/xref/frameworks/support/v7/recyclerview/src/android/support/v7/widget/RecyclerView.java) 設計與實現(源碼解讀RecyclerView )**
RecyclerView有很多內部類和接口,具體如下圖所示:
:-: 
RecyclerView的內部類
對于RecyclerView 和ListView 來說,比較**相同的一點是使用了Adapter 和觀察者模式**,相關的代碼如下。
**RecyclerView.java**
~~~
package android.support.v7.widget;
public class RecyclerView extends ViewGroup {
private static final String TAG = "RecyclerView";
..............
public void setAdapter(Adapter adapter) {
setAdapterInternal(adapter, false, true);
requestLayout();
}
private void setAdapterInternal(Adapter adapter, boolean compatibleWithPrevious,
boolean removeAndRecycleViews) {
if (mAdapter != null) {
mAdapter.unregisterAdapterDataObserver(mObserver);
}
if (!compatibleWithPrevious || removeAndRecycleViews) {
if (mItemAnimator != null) {
mItemAnimator.endAnimations();
}
if (mLayout != null) {
mLayout.removeAndRecycleAllViews(mRecycler);
mLayout.removeAndRecycleScrapInt(mRecycler, true);
}
}
mAdapterHelper.reset();
final Adapter oldAdapter = mAdapter;
mAdapter = adapter;
if (adapter != null) {
//注冊觀察者
adapter.registerAdapterDataObserver(mObserver);
}
if (mLayout != null) {
mLayout.onAdapterChanged(oldAdapter, mAdapter);
}
mRecycler.onAdapterChanged(oldAdapter, mAdapter, compatibleWithPrevious);
//設置結構發生改變的標志位
mState.mStructureChanged = true;
// 刷新視圖
markKnownViewsInvalid();
}
............
}
~~~
在**用setAdapter時最終也會注冊一個觀察者**,這個觀察者具體實現類是RecyclerView 的內部類**RecyclerViewDataObserver**, 具體代碼如下。
**RecyclerView.RecyclerViewDataObserver**
~~~
private class RecyclerViewDataObserver extends AdapterDataObserver {
@Override
public void onChanged() {
assertNotInLayoutOrScroll(null);
if (mAdapter.hasStableIds()) {
mState.mStructureChanged = true;
mDataSetHasChangedAfterLayout = true;
} else {
mState.mStructureChanged = true;
mDataSetHasChangedAfterLayout = true;
}
//需要重新布局
if (!mAdapterHelper.hasPendingUpdates()) {
requestLayout();
}
}
@Override
public void onItemRangeChanged(int positionStart, int itemCount) {
assertNotInLayoutOrScroll(null);
if (mAdapterHelper.onItemRangeChanged(positionStart, itemCount)) {
triggerUpdateProcessor();
}
}
@Override
public void onItemRangeInserted(int positionStart, int itemCount) {
assertNotInLayoutOrScroll(null);
if (mAdapterHelper.onItemRangeInserted(positionStart, itemCount)) {
triggerUpdateProcessor();
}
}
@Override
public void onItemRangeRemoved(int positionStart, int itemCount) {
assertNotInLayoutOrScroll(null);
if (mAdapterHelper.onItemRangeRemoved(positionStart, itemCount)) {
triggerUpdateProcessor();
}
}
@Override
public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) {
assertNotInLayoutOrScroll(null);
if (mAdapterHelper.onItemRangeMoved(fromPosition, toPosition, itemCount)) {
triggerUpdateProcessor();
}
}
void triggerUpdateProcessor() {
if (mPostUpdatesOnAnimation && mHasFixedSize && mIsAttached) {
ViewCompat.postOnAnimation(RecyclerView.this, mUpdateChildViewsRunnable);
} else {
mAdapterUpdateDuringMeasure = true;
requestLayout();
}
}
}
~~~
**在數據集發生變化且調用了Adapter 的notifyDataSetChanged之后就會調用RecyclerViewDataObserver 的onChanged 函數,在該函數中又會調用RecyclerView 的requestLayout函數進行重新布局**。這些過程與ListView 的實現基本一致,最大的不同在于它們之間的布局實現上。**在ListView中的布局是通過自身的layoutChilden 函數來實現,而對于RecyclerView 來說它的布局職責則是交給了LayoutManager 對象**。
**RecyclerView:setLayoutManager()**
~~~
package android.support.v7.widget;
public class RecyclerView extends ViewGroup {
private static final String TAG = "RecyclerView";
..............
private Adapter mAdapter;
private LayoutManager mLayout;
............
public void setLayoutManager(LayoutManager layout) {
if (layout == mLayout) {
return;
}
if (mLayout != null) {
if (mIsAttached) {
mLayout.onDetachedFromWindow(this, mRecycler);
}
mLayout.setRecyclerView(null);
}
mRecycler.clear();
mChildHelper.removeAllViewsUnfiltered();
mLayout = layout;
if (layout != null) {
if (layout.mRecyclerView != null) {
throw new IllegalArgumentException("LayoutManager " + layout +
" is already attached to a RecyclerView: " + layout.mRecyclerView);
}
mLayout.setRecyclerView(this);
if (mIsAttached) {
mLayout.onAttachedToWindow(this);
}
}
//設置布局管理器之后重新布局
requestLayout();
}
............
@Override
public void requestLayout() {
if (!mEatRequestLayout) {
super.requestLayout();
} else {
mLayoutRequestEaten = true;
}
}
..................
}
~~~
**在設置了布局管理器之后就會調用requestLayout 函數進行布局,然后會調用onLayout 函數**
**RecyclerView:onLayout()、dispatchLayout()**
~~~
package android.support.v7.widget;
public class RecyclerView extends ViewGroup {
private static final String TAG = "RecyclerView";
..............
void dispatchLayout() {
.............
//獲取工tern 數量
mState.mItemCount = mAdapter.getItemCount();
mState.mDeletedInvisibleItemCountSincePreviousLayout = 0;
// Step 2: Run layout
mState.mInPreLayout = false;
//執行布局,調用LayoutManager 的onLayoutChilden函數
mLayout.onLayoutChildren(mRecycler, mState);
mState.mStructureChanged = false;
mPendingSavedState = null;
mState.mRunSimpleAnimations = mState.mRunSimpleAnimations && mItemAnimator != null;
// 含有動畫則執行item 動畫
if (mState.mRunSimpleAnimations) {
.................
}
................
}
............
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
eatRequestLayout();
//分發layout
dispatchLayout();
resumeRequestLayout(false);
mFirstLayoutComplete = true;
}
..............
}
~~~
**在onLayout 函數中最終會調用dispatchLayout 函數,而在dispatchLayout 函數中又會調用LayoutManager 的onLayoutChilden 函數進行布局**。在此,我們以LinearLayoutManager為例進行學習。下面看看[LinearLayoutManager](https://www.androidos.net.cn/android/5.0.1_r1/xref/frameworks/support/v7/recyclerview/src/android/support/v7/widget/LinearLayoutManager.java) 中的onLayoutChilden 函數。
**LinearLayoutManager:onLayoutChildren()、fill()**
~~~
package android.support.v7.widget;
public class LinearLayoutManager extends RecyclerView.LayoutManager {
private static final String TAG = "LinearLayoutManager";
...............
/**
* {@inheritDoc}
*/
@Override
public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
..............
int startOffset;
int endOffset;
onAnchorReady(state, mAnchorInfo);
detachAndScrapAttachedViews(recycler);
mLayoutState.mIsPreLayout = state.isPreLayout();
if (mAnchorInfo.mLayoutFromEnd) {
.............
} else {
// 從上到下布局
updateLayoutStateToFillEnd(mAnchorInfo);
mLayoutState.mExtra = extraForEnd;
fill(recycler, mLayoutState, state, false);
endOffset = mLayoutState.mOffset;
if (mLayoutState.mAvailable > 0) {
extraForStart += mLayoutState.mAvailable;
}
// fill towards start
updateLayoutStateToFillStart(mAnchorInfo);
mLayoutState.mExtra = extraForStart;
mLayoutState.mCurrentPosition += mLayoutState.mItemDirection;
//填充Item View
fill(recycler, mLayoutState, state, false);
startOffset = mLayoutState.mOffset;
}
..............
}
..............
int fill(RecyclerView.Recycler recycler, LayoutState layoutState,
RecyclerView.State state, boolean stopOnFocusable) {
// 存儲當前可用空間
final int start = layoutState.mAvailable;
................
// 1.計算RecyclerView 的可用布局寬或高
int remainingSpace = layoutState.mAvailable + layoutState.mExtra;
LayoutChunkResult layoutChunkResult = new LayoutChunkResult();
// 2.迭代布局item view
while (remainingSpace > 0 && layoutState.hasMore(state)) {
layoutChunkResult.resetInternal();
//3.布局item View
layoutChunk(recycler, state, layoutState, layoutChunkResult);
if (layoutChunkResult.mFinished) {
break;
}
// 4.計算布局偏移量
layoutState.mOffset += layoutChunkResult.mConsumed * layoutState.mLayoutDirection;
if (!layoutChunkResult.mIgnoreConsumed || mLayoutState.mScrapList != null
|| !state.isPreLayout()) {
layoutState.mAvailable -= layoutChunkResult.mConsumed;
// 5.計算剩余的可用空間
remainingSpace -= layoutChunkResult.mConsumed;
}
................
}
if (DEBUG) {
validateChildOrder();
}
return start - layoutState.mAvailable;
}
..............
}
~~~
**在onLayoutChilden 函數中會調用fill函數,在fill 函數中又會循環地調用layoutChunk 函數進行布局,每次布局完之后就會計算當前屏幕剩余的可利用空間,并且做出判斷是否還需要布局ItemView**。因此,先看看layoutChunk的實現。
**LinearLayoutManager:layoutChunk()**
~~~
void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state,
LayoutState layoutState, LayoutChunkResult result) {
// 1.獲取Item View
View view = layoutState.next(recycler);
...............
//2.獲取Item View的布局參數
RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) view.getLayoutParams();
// 3.丈量Item View
measureChildWithMargins(view, 0, 0);
// 4.計算該Item View消耗的寬度或高度
result.mConsumed = mOrientationHelper.getDecoratedMeasurement(view);
// Item View的上下左右坐標位置
int left, top, right, bottom;
// 5.按照水平或豎直方向布局,計算Item View 的上下左右坐標
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 {
//豎直方向布局的計算方式
}
// 6 . 布局item view
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));
}
if (params.isItemRemoved() || params.isItemChanged()) {
result.mIgnoreConsumed = true;
}
result.mFocusable = view.isFocusable();
}
~~~
**在layoutChunk 中首先從layoutState 中獲取到Item View ,然后獲取Item View 的布局參數、尺寸信息,并且根據布局方式(橫向或者縱向)計算出Item View的上下左右坐標,最后調用layoutDecorated 函數實現布局**。
layoutDecorated 函數定義在LayoutManager 中,具體代碼如下。
**RecyclerView.LayoutManager:layoutDecorated()**
~~~
public void layoutDecorated(View child, int left, int top, int right, int bottom) {
final Rect insets = ((LayoutParams) child.getLayoutParams()).mDecorInsets;
child.layout(left + insets.left, top + insets.top, right - insets.right,
bottom - insets.bottom);
}
~~~
**從上述程序可以看到, 只是調用了Item View 的layout函數( child.layout函數)將Item View 布局到具體的位置。這樣一來,就將布局的職責從RecyclerView 分離到LayoutManager 中,也使得RecyclerView 更為靈活。**
這里的**layoutChunk 函數很重要**,**首先通過LayoutState 對象的next 函數獲取到Item View**,這里也是一個重要的地方。我們看看LayoutState 函數的next 實現。
**LinearLayoutManager.LayoutState:next()**
~~~
package android.support.v7.widget;
public class LinearLayoutManager extends RecyclerView.LayoutManager {
private static final String TAG = "LinearLayoutManager";
...............
static class LayoutState {
..............
View next(RecyclerView.Recycler recycler) {
if (mScrapList != null) {
return nextFromLimitedList();
}
//調用Recycler中的getViewForPosition獲取item View
final View view = recycler.getViewForPosition(mCurrentPosition);
mCurrentPosition += mItemDirection;
return view;
}
.............
}
..............
}
~~~
**實際上就是調用RecyclerView.Recycler 對象getViewForPosition 函數獲取到Item View** ,我們繼續深RecyclerView.Recycler 類的相關代碼。
**RecyclerView.Recycler:getViewForPosition()、getChangedScrapViewForPosition()、getScrapViewForPosition()**
~~~
package android.support.v7.widget;
public class RecyclerView extends ViewGroup {
private static final String TAG = "RecyclerView";
..............
public final class Recycler {
final ArrayList<ViewHolder> mAttachedScrap = new ArrayList<ViewHolder>();
private ArrayList<ViewHolder> mChangedScrap = null;
final ArrayList<ViewHolder> mCachedViews = new ArrayList<ViewHolder>();
................
//根據position 獲取該位置對應的View
public View getViewForPosition(int position) {
return getViewForPosition(position, false);
}
View getViewForPosition(int position, boolean dryRun) {
if (position < 0 || position >= mState.getItemCount()) {
throw new IndexOutOfBoundsException("Invalid item position " + position
+ "(" + position + "). Item count:" + mState.getItemCount());
}
boolean fromScrap = false;
ViewHolder holder = null;
// 1.從mChangedScrap中獲取ViewHolder緩存
if (mState.isPreLayout()) {
holder = getChangedScrapViewForPosition(position);
fromScrap = holder != null;
}
// 2.從mAttachedScrap中獲取ViewHolder緩存
if (holder == null) {
holder = getScrapViewForPosition(position, INVALID_TYPE, dryRun);
..................
}
if (holder == null) {
final int offsetPosition = mAdapterHelper.findPositionOffset(position);
.................
//從其他ViewHolder緩存中檢測是否有緩存,代碼省略
// 3.沒有ViewHolder,則需要創建ViewHolder,這里會調用onCreateViewHolder 函數
if (holder == null) {
holder = mAdapter.createViewHolder(RecyclerView.this,
mAdapter.getItemViewType(offsetPosition));
if (DEBUG) {
Log.d(TAG, "getViewForPosition created new ViewHolder");
}
}
}
boolean bound = false;
if (mState.isPreLayout() && holder.isBound()) {
holder.mPreLayoutPosition = position;
} else if (!holder.isBound() || holder.needsUpdate() || holder.isInvalid()) {
if (DEBUG && holder.isRemoved()) {
throw new IllegalStateException("Removed holder should be bound and it should"
+ " come here only in pre-layout. Holder: " + holder);
}
final int offsetPosition = mAdapterHelper.findPositionOffset(position);
// 4.綁定數據,這里會調用Adapter的onBindViewHolder
mAdapter.bindViewHolder(holder, offsetPosition);
attachAccessibilityDelegate(holder.itemView);
bound = true;
if (mState.isPreLayout()) {
holder.mPreLayoutPosition = position;
}
}
// 設置Item View 的LayoutParams
final ViewGroup.LayoutParams lp = holder.itemView.getLayoutParams();
final LayoutParams rvLayoutParams;
if (lp == null) {
rvLayoutParams = (LayoutParams) generateDefaultLayoutParams();
holder.itemView.setLayoutParams(rvLayoutParams);
} else if (!checkLayoutParams(lp)) {
rvLayoutParams = (LayoutParams) generateLayoutParams(lp);
holder.itemView.setLayoutParams(rvLayoutParams);
} else {
rvLayoutParams = (LayoutParams) lp;
}
rvLayoutParams.mViewHolder = holder;
rvLayoutParams.mPendingInvalidate = fromScrap && bound;
// 5.返回itemView
return holder.itemView;
}
................
ViewHolder getChangedScrapViewForPosition(int position) {
// If pre-layout, check the changed scrap for an exact match.
final int changedScrapSize;
if (mChangedScrap == null || (changedScrapSize = mChangedScrap.size()) == 0) {
return null;
}
// find by position
for (int i = 0; i < changedScrapSize; i++) {
final ViewHolder holder = mChangedScrap.get(i);
if (!holder.wasReturnedFromScrap() && holder.getPosition() == position) {
holder.addFlags(ViewHolder.FLAG_RETURNED_FROM_SCRAP);
return holder;
}
}
// find by id
if (mAdapter.hasStableIds()) {
final int offsetPosition = mAdapterHelper.findPositionOffset(position);
if (offsetPosition > 0 && offsetPosition < mAdapter.getItemCount()) {
final long id = mAdapter.getItemId(offsetPosition);
for (int i = 0; i < changedScrapSize; i++) {
final ViewHolder holder = mChangedScrap.get(i);
if (!holder.wasReturnedFromScrap() && holder.getItemId() == id) {
holder.addFlags(ViewHolder.FLAG_RETURNED_FROM_SCRAP);
return holder;
}
}
}
}
return null;
}
ViewHolder getScrapViewForPosition(int position, int type, boolean dryRun) {
final int scrapCount = mAttachedScrap.size();
// Try first for an exact, non-invalid match from scrap.
for (int i = 0; i < scrapCount; i++) {
final ViewHolder holder = mAttachedScrap.get(i);
if (!holder.wasReturnedFromScrap() && holder.getPosition() == position
&& !holder.isInvalid() && (mState.mInPreLayout || !holder.isRemoved())) {
if (type != INVALID_TYPE && holder.getItemViewType() != type) {
Log.e(TAG, "Scrap view for position " + position + " isn't dirty but has" +
" wrong view type! (found " + holder.getItemViewType() +
" but expected " + type + ")");
break;
}
holder.addFlags(ViewHolder.FLAG_RETURNED_FROM_SCRAP);
return holder;
}
}
if (!dryRun) {
View view = mChildHelper.findHiddenNonRemovedView(position, type);
if (view != null) {
// ending the animation should cause it to get recycled before we reuse it
mItemAnimator.endAnimation(getChildViewHolder(view));
}
}
// Search in our first-level recycled view cache.
final int cacheSize = mCachedViews.size();
for (int i = 0; i < cacheSize; i++) {
final ViewHolder holder = mCachedViews.get(i);
if (!holder.isInvalid() && holder.getPosition() == position) {
if (!dryRun) {
mCachedViews.remove(i);
}
if (DEBUG) {
Log.d(TAG, "getScrapViewForPosition(" + position + ", " + type +
") found match in cache: " + holder);
}
return holder;
}
}
return null;
}
..............
}
..............
}
~~~
我們知道在**RecyclerView的Adapter 中被緩存的單位已經不是Item View 了, 而是一個View Holder** , 而原來的ListView 則是緩存的View 。在**RecyclerView. Recycler 類中有mAttachedScrap、mChangedScrap、mCachedViews 幾個ViewHolder 列表對象**,它們就是**用于緩存ViewHolder** 的。
**在通過LayoutState 的next 函數獲取Item View 時, 實際上調用的是RecyclerView.Recycler的getViewForPosition函數**, **該函數首先會從這幾個ViewHolder 緩存中獲取對應位置的ViewHolder,如果沒有緩存則調用RecyclerView.Adapter中的createView Holder 函數來創建一個新的ViewHolder**,我們看看RecyclerView.Adapter 的createViewHolder 函數。
**RecyclerView.Adapter:createViewHolder()**
~~~
package android.support.v7.widget;
public class RecyclerView extends ViewGroup {
private static final String TAG = "RecyclerView";
..............
public static abstract class Adapter<VH extends ViewHolder> {
private final AdapterDataObservable mObservable = new AdapterDataObservable();
private boolean mHasStableIds = false;
............
public final VH createViewHolder(ViewGroup parent, int viewType) {
// 調用onCreateViewHolder 創建ViewHolder,用戶需要覆寫onCreateViewHolder函數
final VH holder = onCreateViewHolder(parent, viewType);
holder.mItemViewType = viewType;
return holder;
}
//創建ViewHolder,子類需覆寫
public abstract VH onCreateViewHolder(ViewGroup parent, int viewType);
}
..............
}
~~~
在**createViewHolder 函數中實際上調用了onCreateViewHolder 函數創建ViewHolder 對象。這也就是為什么在繼承RecyclerView.Adapter 時,需要覆寫onCreateViewHolder 函數, 并且在該函數中返回ViewHolder 的原因**。
**通過這個onCreateViewHolder 函數會加載Item View視圖, 并且把Item View 當作ViewHolder 的構造參數傳遞給ViewHolder(這個自定義的ViewHolder繼承于RecyclerView.ViewHolder),此時ViewHolder 就構建完畢了**。調用了Adapter 的createViewHolder 后,此時執行到Recycler 的getViewForPosition 函數的注釋4 處,也就是調用了Adapter 中的bindViewHolder 函數, 相關代碼如下。
**RecyclerView.Adapter:bindViewHolder()**
~~~
public final void bindViewHolder(VH holder, int position) {
holder.mPosition = position;
if (hasStableIds()) {
holder.mItemId = getItemId(position);
}
//調用onBindViewHolder綁定數據
onBindViewHolder(holder, position);
//設置holder 的flags
holder.setFlags(ViewHolder.FLAG_BOUND,
ViewHolder.FLAG_BOUND | ViewHolder.FLAG_UPDATE | ViewHolder.FLAG_INVALID);
}
//綁定數據, 子類需覆寫
public abstract void onBindViewHolder(VH holder, int position);
~~~
bindViewHolder 函數與createViewHolder 如出一轍,只是它調用的是onBindViewHolder 函數。與onCreateViewHolder 一樣, **onBindViewHolder 也需要子類覆寫, 并且在這個函數中進行數據綁定**。在getViewForPosition 中實際上相當于一個模板方法,它封裝了獲取、綁定ViewHolder 的過程,子類只需要覆寫特定的函數即可完成這個過程。
執行完onBindViewHolder 函數之后數據就被綁定到Item View 上了。
我們最后再來分析一下這個過程。
與ListView 一樣, **RecyclerView 還是通過Adapter 和觀察者模式進行數據綁定,使得Recycler View 的靈活性得到保證**。**RecyclerView 的Adapter 并不是ListView 中的Adapter,它封裝了ViewHolder 的創建與綁定等邏輯,降低了用戶的使用成本。RecyclerView 也將緩存單元從Item View 換成了ViewHolder,在一定程度上建立起了規范**。
RecyclerView 與ListView **最大的不同是RecyclerView 將布局的工作交給了LayoutManager,在LayoutManager 的onLayou.tChilden 中對Item View 進行布局、執行動畫等操作**,這樣一來,**使得RecyclerView 可以動態地替換掉布局方式**,例如,在運行時給RecyclerView 重新設置一個LayoutManager 就可以將原來是線性布局的視圖改成網格布局,這大大地增加了靈活性。將布局職責獨立出來也符合單一職責原則,而使用組合代替繼承也會減少精合、增強健壯性,也使得RecyclerView 的布局具有更好的擴展性。