🔥码云GVP开源项目 12k star Uniapp+ElementUI 功能强大 支持多语言、二开方便! 广告
#### **[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有很多内部类和接口,具体如下图所示: :-: ![](https://box.kancloud.cn/0dc9cd0a0605e772a586e596b5599436_362x438.jpg) 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 的布局具有更好的扩展性。