技术不止,文章有料,关注公众号
九心说
,每周一篇高质好文,和九心在大厂路上肩并肩。android
抽丝剥茧RecyclerView
系列文章的目的在于帮助Android开发者提升对RecyclerView的认知,本文是整个系列的第一章。设计模式
RecyclerView
已经出来好久了,不少开发者对于RecyclerView
的使用早已信手拈来。以下就是一张使用网格布局的RecyclerView
: 不过,对于
RecyclerView
这种明星控件的了解仅停留在使用的程度,显然是不可以让咱们成为高级工程师的。若是你看过RecyclerView
包中的源码,那你应该和个人心情同样复杂,光一个RecyclerView.class
文件的源码就多达13000行。缓存
对于源码阅读方式,我很同意郭神在Glide源码分析中所说:markdown
抽丝剥茧、点到即止。应该认准一个功能点,而后去分析这个功能点是如何实现的。但只要去追寻主体的实现逻辑便可,千万不要试图去搞懂每一行代码都是什么意思,那样很容易会陷入到思惟黑洞当中,并且越陷越深。app
因此,我在阅读RecyclerView
源码的时候先肯定好本身想好了解的功能点:ide
阅读姿式 :我选择了版本为27.1.1
的RecyclerView
,不知道什么缘由,我点进28.0.0
版本的RecyclerView库
中查看RecyclerView.class
代码时,虽然类缩短至7000行,可是注释没了以及其余的问题,我不得不使用其余版本的RecyclerView
库。函数
想要深刻原理,没有什么是一遍调试解决不了的,若是有,那就是调试第二遍。源码分析
以LinearLayoutManager
为例,咱们看一下RecyclerView
的使用方式:布局
RecyclerView mRecyclerView = findViewById(R.id.recycle);
// 设置布局方式
mRecyclerView.setLayoutManager(new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false));
// 适配器,MainAdapter继承自RecyclerView.Adapter<VH extends RecyclerView.ViewHolder>
MainAdapter mAdapter = new MainAdapter();
mRecyclerView.setAdapter(mAdapter);
// 添加分割线的方法
// mRecyclerView.addItemDecoration();
// 设置布局动画的方法,能够自定义
// mRecyclerView.setItemAnimator();
复制代码
以及RecyclerView
各个部分的做用:post
主要的类 | 做用 |
---|---|
LayoutManager |
负责RecyclerView 子View 的布局,经常使用的有LinearLayoutManager (线性布局),还有GridLayoutManager (网格布局)和StaggeredGridLayoutManager (瀑布布局)等。 |
Adapter |
负责将数据转变成视图,使用时须要继承该类。 |
ItemAnimator |
子视图动画,RecyclerView 有默认的子视图动画,也可自定义实现。 |
ItemDecoration |
分隔线,需自定义实现。 |
以上是咱们使用RecyclerView 的时候可以直观看到的部分,还有一个很重要可是不直接使用的类: |
|
主要的类 | 做用 |
:--: | :-- |
Recycler |
负责ViewHolder 的回收和提供。 |
## 2、源码分析 | |
### 1. RecyclerView三大工做流程 | |
RecyclerView 的源码那么多,咱们先按照使用时的路线进行分析。 |
|
##### 1.1 构造函数 | |
一般,咱们会在布局文件中使用RecyclerView ,因此咱们的入口就变成了: |
|
``` | |
public RecyclerView(@NonNull Context context, @Nullable AttributeSet attrs) { |
this(context, attrs, 0);
复制代码
}
public RecyclerView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); // ... 省略一些实例的初始化
if (attrs != null) {
int defStyleRes = 0;
TypedArray a = context.obtainStyledAttributes(attrs, styleable.RecyclerView, defStyle, defStyleRes);
String layoutManagerName = a.getString(styleable.RecyclerView_layoutManager);
// ... 这里惟一值得关注就是看布局文件是否指定LayoutManager
a.recycle();
this.createLayoutManager(context, layoutManagerName, attrs, defStyle, defStyleRes);
// ...
} else {
// ...
}
// ...
复制代码
}
因为咱们能够在`RecyclerView`的布局文件中使用` app:layoutManager`指定`LayoutManager`,若是指定了具体的`LayoutManager`,最终会在上面的`RecyclerView#createLayoutManager`方法中利用**反射**生成一个具体的`LayoutManager`实例。
##### 1.2 设置LayoutManager和Adapter
研究自定义View的时候,最快的研究方法就是直接查看`onMeasure`、`onLayout`和`onDraw`三大方法,研究`RecyclerView`也是如此。
上面咱们说到了布局文件,以后,咱们会在`Activity`或者其余地方获取`RecyclerView`,再往下,咱们会为`RecyclerView`设置`LayoutManager`(如未在布局文件中设置的状况下)、`Adapter`以及可能使用的`ItemDecoration`,这些方法都会调用`RecyclerView#requestLayout`方法,从而刷新`RecyclerView`。
先从`RecyclerView#setLayoutManager`讲起:
复制代码
public void setLayoutManager(@Nullable RecyclerView.LayoutManager layout) { if (layout != this.mLayout) { // 中止滚动 this.stopScroll(); if (this.mLayout != null) { // 由于是第一次设置,因此mLayout为空 // ... 代码省略 主要是对以前的LayoutManager 进行移除前的操做 } else { this.mRecycler.clear(); } this.mChildHelper.removeAllViewsUnfiltered(); this.mLayout = layout; if (layout != null) { // 对新的LayoutManager进行设置 this.mLayout.setRecyclerView(this); if (this.mIsAttached) { this.mLayout.dispatchAttachedToWindow(this); } } this.mRecycler.updateViewCacheSize(); // 重点 通知界面从新布局和重绘 this.requestLayout(); } }
`RecyclerView#requestLayout`会刷新布局,因此该跳到`ViewGroup`绘制的相关方法了?不,由于`RecyclView`中的`Adapter`为空,`Adapter`为空,就没有数据,那看一个空视图还有什么意思呢?So,咱们还须要看设置适配器的`RecyclerView#setAdapter`方法:
复制代码
public void setAdapter(@Nullable RecyclerView.Adapter adapter) { // 冻结当前布局,不让进行子布局的更新 this.setLayoutFrozen(false); // 重点关注的方法 this.setAdapterInternal(adapter, false, true); this.processDataSetCompletelyChanged(false); // 再次请求布局的从新绘制 this.requestLayout(); }
继续深刻查看`RecyclerView#setAdapterInternal`方法:
复制代码
private void setAdapterInternal(@Nullable RecyclerView.Adapter adapter, Boolean compatibleWithPrevious, Boolean removeAndRecycleViews) { if (this.mAdapter != null) { // 第一次进入mAdapter为null,故不会进入该代码块 // 主要是对旧的mAdapter的数据监听器解除注册 this.mAdapter.unregisterAdapterDataObserver(this.mObserver); this.mAdapter.onDetachedFromRecyclerView(this); } if (!compatibleWithPrevious || removeAndRecycleViews) { // 更换适配器的时候移除全部的子View this.removeAndRecycleViews(); } this.mAdapterHelper.reset(); RecyclerView.Adapter oldAdapter = this.mAdapter; this.mAdapter = adapter; if (adapter != null) { // 新的适配器注册数据监听器 adapter.registerAdapterDataObserver(this.mObserver); adapter.onAttachedToRecyclerView(this); } if (this.mLayout != null) { this.mLayout.onAdapterChanged(oldAdapter, this.mAdapter); } this.mRecycler.onAdapterChanged(oldAdapter, this.mAdapter, compatibleWithPrevious); this.mState.mStructureChanged = true; }
能够看出,上面的代码主要是针对`Adapter`发生变化的状况下作出的一些修改,`RecyclerView.AdapterDataObserver`是数据变化接口,当适配器中的数据发生增删改的时候最终会调用该接口的实现类,从该接口的命名以及注册操做和取消注册操做能够看出其使用的是观察者模式。`LayoutManager`和`Adapter`设置完成之后就能够直奔主题了。
##### 1.3 onMeasure
View工做流程的第一步:
复制代码
protected void onMeasure(int widthSpec, int heightSpec) { if (this.mLayout == null) { this.defaultOnMeasure(widthSpec, heightSpec); } else { // LinearLayoutManager#isAutoMeasureEnabled为True // GridLayoutManager继承子LinearLayoutManager isAutoMeasureEnabled一样为true // 这种状况下,咱们主要分析this.mLayout.isAutoMeasureEnabled()为true的场景下 if (!this.mLayout.isAutoMeasureEnabled()) { // ... 省略 } else { int widthMode = MeasureSpec.getMode(widthSpec); int heightMode = MeasureSpec.getMode(heightSpec); // ... 测量 最后仍是走ViewGroup测量子布局的那套 this.mLayout.onMeasure(this.mRecycler, this.mState, widthSpec, heightSpec); Boolean measureSpecModeIsExactly = widthMode == MeasureSpec.EXACTLY && heightMode == MeasureSpec.EXACTLY; // 若是当前的RecyclerView的布局方式是设置了具体高宽或Match_Parent或mAdapter为null就直接返回 if (measureSpecModeIsExactly || this.mAdapter == null) { return; } if (this.mState.mLayoutStep == State.STEP_START) { this.dispatchLayoutStep1(); } this.mLayout.setMeasureSpecs(widthSpec, heightSpec); this.mState.mIsMeasuring = true; this.dispatchLayoutStep2(); this.mLayout.setMeasuredDimensionFromChildren(widthSpec, heightSpec); if (this.mLayout.shouldMeasureTwice()) { this.mLayout.setMeasureSpecs(MeasureSpec.makeMeasureSpec(this.getMeasuredWidth(), MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(this.getMeasuredHeight(), MeasureSpec.EXACTLY)); this.mState.mIsMeasuring = true; this.dispatchLayoutStep2(); this.mLayout.setMeasuredDimensionFromChildren(widthSpec, heightSpec); } } } }
显然,从上面的代码咱们能够得出结论:**`measureSpecModeIsExactly`为`true`或者`Adapter`为空,咱们会提早结束`onMeasure`的测量过程。**
若是看过`View的工做流程`的同窗应该对`SpecMode`很熟悉,什么状况下`SpecMode`会为`EXACITY `呢?以`RecyclerView`为例,一般状况下,若是`RecyclerView`的宽为具体数值或者`Match_Parent`的时候,那么它的`SpecMode`很大程度就为`EXACITY `。`measureSpecModeIsExactly`为`true`须要保证高和宽的`SpecMode`都为`EXACITY `,固然,**`View`的`SpecMode`还与父布局有关**,不了解的的同窗能够查阅一下相关的资料。
若是你的代码中的RecyclerView没有使用`Wrap_Content`,那么大部分使用场景中的`RecyclerView`长宽的`SpecMode`都为`EXACITY`,我这么说,不是意味着我要抛弃`return`下方的关键方法`RecyclerView#dispatchLayoutStep1`和`RecyclerView#dispatchLayoutStep2`,由于它们在另外一个工做流程`onLayout`中也会执行,因此咱们放到`onLayout`中讲解。
##### 1.4 onLayout
View工做流程的第二步:
复制代码
protected void onLayout(Boolean changed, int l, int t, int r, int b) { TraceCompat.beginSection("RV OnLayout"); this.dispatchLayout(); TraceCompat.endSection(); this.mFirstLayoutComplete = true; }
void dispatchLayout() { if (this.mAdapter == null) { // ... } else if (this.mLayout == null) { // ... } else { this.mState.mIsMeasuring = false; // 根据当前State的不一样执行不一样的流程 if (this.mState.mLayoutStep == 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(); } this.dispatchLayoutStep3(); } }
在`mState`实例初始化中,`mState.mLayoutStep`默认为`STEP_START`,`RecyclerView#dispatchLayoutStep1`方法确定是要进入的:
复制代码
private void dispatchLayoutStep1() { // 所有清空位置信息 mViewInfoStore.clear(); // 肯定mState.mRunSimpleAnimations和mState.mRunPredictiveAnimations // ... // 预布局状态跟mState.mRunPredictiveAnimations相关 mState.mInPreLayout = mState.mRunPredictiveAnimations; // ... 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) { // ... // 存储子View的位置信息... mViewInfoStore.addToPreLayout(holder, animationInfo); } } if (mState.mRunPredictiveAnimations) { // 其实我也不太理解PreLayout布局的意义,放出来看看 // Step 1: run prelayout: This will use the old positions of items. The layout manager // is expected to layout everything, even removed items (though not to add removed // items back to the container). This gives the pre-layout position of APPEARING views // which come into existence as part of the real layout.
// 真实布局以前尝试布局一次
// temporarily disable flag because we are asking for previous layout
mLayout.onLayoutChildren(mRecycler, mState);
for (int i = 0; i < mChildHelper.getChildCount(); ++i) {
//...
if (!mViewInfoStore.isInPreLayout(viewHolder)) {
// ...
if (wasHidden) {
recordAnimationInfoIfBouncedHiddenView(viewHolder, animationInfo);
} else {
mViewInfoStore.addToAppearedInPreLayoutHolders(viewHolder, animationInfo);
}
}
}
// we don't process disappearing list because they may re-appear in post layout pass.
clearOldPositions();
} else {
clearOldPositions();
}
//
mState.mLayoutStep = State.STEP_LAYOUT;
复制代码
}
private void processAdapterUpdatesAndSetAnimationFlags() { // ... // mFirstLayoutComplete 会在RecyclerView第一次完成onLayout变为True Boolean animationTypeSupported = mItemsAddedOrRemoved || mItemsChanged; mState.mRunSimpleAnimations = mFirstLayoutComplete && mItemAnimator != null && (mDataSetHasChangedAfterLayout || animationTypeSupported || mLayout.mRequestedSimpleAnimations) && (!mDataSetHasChangedAfterLayout || mAdapter.hasStableIds()); mState.mRunPredictiveAnimations = mState.mRunSimpleAnimations && animationTypeSupported && !mDataSetHasChangedAfterLayout && predictiveItemAnimationsEnabled(); }
咱们须要关注`mState.mRunSimpleAnimations`和`mState.mRunPredictiveAnimations`为true时机,从代码上来看,这两个属性为true必须存在`mItemAnimator`,是否意味着子View动画的执行者`mItemAnimator`,另外,`mViewInfoStore.addToPreLayout(holder, animationInfo);`也得关注,`ViewInfoStore`帮`RecyclerView`记录了`ViewHolder`中子View的位置信息和状态。
再看`RecyclerView#dispatchLayoutStep2`方法:
复制代码
private void dispatchLayoutStep2() { // ... // 预布局结束 进入真实的布局过程 this.mState.mInPreLayout = false; // 实际的布局交给了LayoutManager this.mLayout.onLayoutChildren(this.mRecycler, this.mState); // ... // 是否有动画 this.mState.mRunSimpleAnimations = this.mState.mRunSimpleAnimations && this.mItemAnimator != null; // 变动状态 准备播放动画 STEP_ANIMATIONS-4 this.mState.mLayoutStep = State.STEP_ANIMATIONS; // ... }
在`RecyclerView#dispatchLayoutStep2`方法中咱们能够看到,`RecyclerView`自身没有实现给子`View`布局,而是将布局方式交给了`LayoutManager`,`LayoutManager`的深刻研究我会在以后的博客和你们讨论。
打铁趁热,咱们查看`RecyclerView#dispatchLayoutStep3`,代码较多,精简后以下:
复制代码
private void dispatchLayoutStep3() { this.mState.assertLayoutStep(State.STEP_ANIMATIONS); // ... 省略 this.mState.mLayoutStep = State.STEP_START; if (this.mState.mRunSimpleAnimations) { for (int i = this.mChildHelper.getChildCount() - 1; i >= 0; --i) { // ...省略 // 总结下来就是两个步骤: // 1.添加真实的布局信息 this.mViewInfoStore.addToPostLayout(holder, animationInfo); } // 2.挨个执行动画 this.mViewInfoStore.process(this.mViewInfoProcessCallback); } //... 清空信息 this.mViewInfoStore.clear(); }
调用执行动画函数`ViewInfoStore#process`的时候,能够看到放入参数`mViewInfoProcessCallback`,从名字能够看出,这是一个回调的接口,因此,我猜动画的真实的执行应该在实现接口的方法中实现,不过,咱们仍是要先看`ViewInfoStore`中的动画如何执行:
复制代码
void process(ProcessCallback callback) { for (int index = mLayoutHolderMap.size() - 1; index >= 0; index --) { final ViewHolder viewHolder = mLayoutHolderMap.keyAt(index); final InfoRecord record = mLayoutHolderMap.removeAt(index); if ((record.flags & FLAG_APPEAR_AND_DISAPPEAR) == FLAG_APPEAR_AND_DISAPPEAR) { // Appeared then disappeared. Not useful for animations. callback.unused(viewHolder); } else if ((record.flags & FLAG_DISAPPEARED) != 0) { // Set as "disappeared" by the LayoutManager (addDisappearingView) if (record.preInfo == null) { // similar to appear disappear but happened between different layout passes. // this can happen when the layout manager is using auto-measure callback.unused(viewHolder); } else { callback.processDisappeared(viewHolder, record.preInfo, record.postInfo); } } else if ((record.flags & FLAG_APPEAR_PRE_AND_POST) == FLAG_APPEAR_PRE_AND_POST) { // Appeared in the layout but not in the adapter (e.g. entered the viewport) callback.processAppeared(viewHolder, record.preInfo, record.postInfo); } else if ((record.flags & FLAG_PRE_AND_POST) == FLAG_PRE_AND_POST) { // Persistent in both passes. Animate persistence callback.processPersistent(viewHolder, record.preInfo, record.postInfo); } else if ((record.flags & FLAG_PRE) != 0) { // Was in pre-layout, never been added to post layout callback.processDisappeared(viewHolder, record.preInfo, null); } else if ((record.flags & FLAG_POST) != 0) { // Was not in pre-layout, been added to post layout callback.processAppeared(viewHolder, record.preInfo, record.postInfo); } else if ((record.flags & FLAG_APPEAR) != 0) { // Scrap view. RecyclerView will handle removing/recycling this. } else if (DEBUG) { throw new IllegalStateException("record without any reasonable flag combination:/"); } // 释放record InfoRecord.recycle(record); } }
// 回调的接口 interface ProcessCallback { void processDisappeared(ViewHolder var1, @NonNull ItemHolderInfo var2, @Nullable ItemHolderInfo var3); void processAppeared(ViewHolder var1, @Nullable ItemHolderInfo var2, ItemHolderInfo var3); void processPersistent(ViewHolder var1, @NonNull ItemHolderInfo var2, @NonNull ItemHolderInfo var3); void unused(ViewHolder var1); }
以前存储的和`ViewHolder`位置状态相关`InfoRecord `被一个个取出,而后将`ViewHolder`和`InfoRecord`交给`ProcessCallback`,如咱们所料,`ViewInfoStore#process`只是对`ViewHolder`进行分类,具体的实现仍是在`RecyclerView`中的回调,最后查看一下具体实现:
复制代码
this.mViewInfoProcessCallback = new ProcessCallback() { // ... 这里咱们只展现一个方法就好了 public void processAppeared(RecyclerView.ViewHolder viewHolder, RecyclerView.ItemAnimator.ItemHolderInfo preInfo, RecyclerView.ItemAnimator.ItemHolderInfo info) { RecyclerView.this.animateAppearance(viewHolder, preInfo, info); } // ... };
void animateAppearance(@NonNull RecyclerView.ViewHolder itemHolder, @Nullable RecyclerView.ItemAnimator.ItemHolderInfo preLayoutInfo, @NonNull RecyclerView.ItemAnimator.ItemHolderInfo postLayoutInfo) { itemHolder.setIsRecyclable(false); if (this.mItemAnimator.animateAppearance(itemHolder, preLayoutInfo, postLayoutInfo)) { this.postAnimationRunner(); } }
限于篇幅,这里我只展现了`ProcessCallback`中实现的一个方法`processAppeared`,在该方法中,它调用了`RecyclerView#animateAppearance`方法,动画的任务最终也交给了`RecyclerView.ItemAnimator`,`RecyclerView.ItemAnimator`可由用户自定义实现。
这里有必要说明一下,一些删除或者新增操做,经过使用适配器中通知删除或者新增的方法,最终仍是会通知界面进行重绘。
到这儿,咱们能够总结一下,`onLayout`过程当中,`RecyclerView`将子视图布局的任务交给了`LayoutMananger`,一样的,子视图动画也不是`RecyclerView`自身完成的,动画任务被交给了`RecyclerView.ItemAnimator`,这也就解决了咱们一开始提出的两个问题:
1. 布局多样性的缘由
2. 布局动画多样性的缘由
至于`LayoutManager`和`RecyclerView.ItemAnimator`更深层次的探讨,我将会在后面的博客中进行。
##### 1.5 onDraw
`RecylcerView`中的`onDraw`方法比较简单,仅仅绘制了`ItemDecoration`,一样须要用户自定义实现:
复制代码
public void onDraw(Canvas c) { super.onDraw(c); int count = this.mItemDecorations.size(); for (int i = 0; i < count; ++i) { ((RecyclerView.ItemDecoration)this.mItemDecorations.get(i)).onDraw(c, this, this.mState); } }
而子View的绘制其实在`ViewGroup#dispatchDraw`实现的,这里再也不继续讨论了。
若是你没看懂,不要紧,`RecyclerView`在三大工程流程中大概作了以下的事:

### 2. View管理-Recycler
在上文中,咱们简要了解**RecyclerView绘制的三大流程以及`LayoutManager`和`ItemAnimator`承担的任务**。显然,咱们忽略了适配器`Adapter`和缓存管理`Recycler`,下面咱们就重点谈谈这两位。
上文中,咱们了解到在`RecyclerView#dispatchLayoutStep2`方法中,给子View定位的任务交给了`LayoutManager`:
复制代码
mLayout.onLayoutChildren(mRecycler, mState);
简要的介绍一下`LayoutManger#onLayoutChildren`的工做内容:
1. 若是当前`RecyclerView`中还存在子`View`,移除全部的子`View`,将移除的`ViewHolder`添加进`Recycler`。
2. 一次经过`Recycler`获取一个子View。
3. 重复进行2,直到获取的子View填充完`RecyclerView`便可。
虽然上面的内容很简单,可是`LayoutManager`的实际工做内容要复杂的多,那么 `Recycler`工做机制是怎样的呢?咱们来一探究竟。
##### 2.1 Recycler重要组成
先看组成部分:
|缓存级别|参与对象|做用|
|:--:|:--|:--|
|一级缓存|`mAttachedScrap`、`mChangedScrap`|`mChangedScrap`仅参与预布局,`mAttachedScrap`存放还会被复用的`ViewHolder`|
|二级缓存|`mCachedViews`|最多存放2个缓存`ViewHolder`|
|三级缓存|`mViewCacheExtension`|需开发者自定义实现|
|四级缓存|`mRecyclerPool`|能够理解`RecyclerPool`是`(int,ArrayList<ViewHolder>)`的`SparseArray`,键是`viewType`,每一个`viewType`最多能够存放5个`ViewHolder`|
##### 2.2 获取ViewHolder
入口是`Recycler#getViewForPosition`,有一个位置的参数:
复制代码
public View getViewForPosition(int position) { return getViewForPosition(position, false); }
// 看函数名称就知道,它是尝试获取ViewHolder View getViewForPosition(int position, Boolean dryRun) { return tryGetViewHolderForPositionByDeadline(position, dryRun, FOREVER_NS).itemView; }
经过名字就能够猜到函数的意思了,`ViewHolder`中的`itemView`就是咱们要获取的子视图,`ViewHolder`是如何获取的呢?
复制代码
ViewHolder tryGetViewHolderForPositionByDeadline(int position, Boolean dryRun, long deadlineNs) { //... ViewHolder holder = null; // 第一步 从 mChangedScrap 中获取 // PreLayout从名字能够看出,它不是真实的布局,不过我不是特别清楚 // 预布局的意义。 // 除此以外,它其实没有意义的,没有参与实际布局的缓存过程当中。 if (mState.isPreLayout()) { holder = getChangedScrapViewForPosition(position); fromScrapOrHiddenOrCache = holder != null; } // 第二步 从 mAttachedScrap或者mCachedViews 中获取 // 若是RecyclerView以前就有ViewHolder,而且这些ViewHolder以后还要 // 继续展示,在Layout过程当中,它会将这些ViewHolder先取出来存放进mAttachedScrap, // 填充的时候再从mAttachedScrap取出 if (holder == null) { holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun); // ... } if (holder == null) { final int offsetPosition = mAdapterHelper.findPositionOffset(position); final int type = mAdapter.getItemViewType(offsetPosition); // 第三步 Find from scrap/cache via stable ids, if exists if (mAdapter.hasStableIds()) { // StableId能够被当作ViewHolder的惟一标识 holder = getScrapOrCachedViewForId(mAdapter.getItemId(offsetPosition), type, dryRun); //... } // 第四步 mViewCacheExtension须要用户自定义实现并设置 if (holder == null && mViewCacheExtension != null) { // We are NOT sending the offsetPosition because LayoutManager does not // know it. final View view = mViewCacheExtension .getViewForPositionAndType(this, position, type); //... } if (holder == null) { // 第五步 从RecycledViewPool中获取 // 经过RecycledViewPool获取 // 每种ViewType的ViewHolder最多能够存放五个 holder = getRecycledViewPool().getRecycledView(type); //... } if (holder == null) { // 第六步 缓存中都没有就从新建立 // 若是缓存中都没有,就须要从新建立 holder = mAdapter.createViewHolder(RecyclerView.this, type); // ... } } Boolean bound = false; if (mState.isPreLayout() && holder.isBound()) { // ... } else if (!holder.isBound() || holder.needsUpdate() || holder.isInvalid()) { // ... // 没有绑定就从新绑定 bound = tryBindViewHolderByDeadline(holder, offsetPosition, position, deadlineNs); } // ... return holder; }
从注释中咱们能够看到,前三步`ViewHolder`的获取是利用的`Recycler`的一级缓存和二级缓存,第四步经过`mViewCacheExtension`获取,第五步经过`RecyuclerPool`的方式获取,若是连缓存池中都没有,那么`Recycler`只好调用`Adapter#createViewHolder`从新建立,这个名称是咱们的老朋友了,并且仍是在`Adapter`中,咱们简单了解一下`Adapter#createViewHolder`:
复制代码
public final VH createViewHolder(ViewGroup parent, int viewType) { // ... final VH holder = onCreateViewHolder(parent, viewType); holder.mItemViewType = viewType; // ... return holder; }
public abstract VH onCreateViewHolder(ViewGroup parent, int viewType);
真正建立`ViewHolder`的是`Adapter#onCreateViewHolder`方法,这也是咱们继承适配器`Adapter`必需要实现的抽象方法,一般,咱们在继承`Adapter`不会只建立`ViewHolder`,还会作子`View`和数据的绑定,在返回视图以前,视图的绑定确定是完成了的,咱们看看视图绑定发生在哪里?
咱们再返回上一个方法`Recycler#tryGetViewHolderForPositionByDeadline`中,能够看到在倒数第四行,在执行`Recycler#tryBindViewHolderByDeadline`方法:
复制代码
private Boolean tryBindViewHolderByDeadline(ViewHolder holder, int offsetPosition, int position, long deadlineNs) { // ... // 最关键的方法就是调用了Adapter#bindViewHolder方法 mAdapter.bindViewHolder(holder, offsetPosition); // ... }
public void onBindViewHolder(VH holder, int position, List payloads) { onBindViewHolder(holder, position); }
public abstract void onBindViewHolder(VH holder, int position);
成功见到咱们必须实现的`Adapter#onBindViewHolder`方法,这些完成之后,子`View`就会被交给`LayoutManager`管理了。
##### 2.2 回收ViewHolder
`ViewHolder`回收的场景有不少种,好比说滑动、数据删除等等。咱们在这里以滑动做为回收的场景,而且只分析手指触摸时的滑动,滑动的入口在`RecyclerView#onTouchEvent`:
复制代码
public Boolean onTouchEvent(MotionEvent e) { // ... switch (action) { // ... case MotionEvent.ACTION_MOVE: { // ... if (mScrollState == SCROLL_STATE_DRAGGING) { mLastTouchX = x - mScrollOffset[0]; mLastTouchY = y - mScrollOffset[1]; // 当前滑动状态设置为SCROLL_STATE_DRAGGING 须要滑动距离大于阈值 if (scrollByInternal( canScrollHorizontally ? dx : 0, canScrollVertically ? dy : 0, vtev)) { getParent().requestDisallowInterceptTouchEvent(true); } // ... } } break; // ... } // ... return true; }
代码简化之后,咱们仅须要关注`RecyclerView#scrollByInternal`:
复制代码
Boolean scrollByInternal(int x, int y, MotionEvent ev) { // ... if (mAdapter != null) { // ... // 不管是横向或者纵向都交给了LayoutManager处理 if (x != 0) { consumedX = mLayout.scrollHorizontallyBy(x, mRecycler, mState); unconsumedX = x - consumedX; } if (y != 0) { consumedY = mLayout.scrollVerticallyBy(y, mRecycler, mState); unconsumedY = y - consumedY; } // ... } // ... return consumedX != 0 || consumedY != 0; }
最后仍是交给了`LayoutManager`处理,除去函数嵌套以后,最后又回到了`LayoutManager`的视图填充的过程,在`2.2章节`中,咱们仅仅讨论了该过程当中视图的获取,其实,该过程当中,还会涉及到视图的回收,`LayoutManager`在回收的过程当中,大概作了以下的事情:
1. 找出须要回收的视图。
2. 通知父布局也就是`RecyclerView`移除子视图。
3. 通知`Recycler`进行回收管理。
咱们着重探究**`Recycler`进行回收管理**,回收的入口是`Recycler#recycleView`:
复制代码
public void recycleView(View view) { // ... ViewHolder holder = getChildViewHolderint(view); // ... recycleViewHolderInternal(holder); }
void recycleViewHolderInternal(ViewHolder holder) { // 一系列检查 // ... Boolean cached = false; Boolean recycled = false; // ... if (forceRecycle || holder.isRecyclable()) { if (mViewCacheMax > 0 && !holder.hasAnyOfTheFlags(ViewHolder.FLAG_INVALID | ViewHolder.FLAG_REMOVED | ViewHolder.FLAG_UPDATE | ViewHolder.FLAG_ADAPTER_POSITION_UNKNOWN)) { // mViewCacheMax 默认最大值为2 int cachedViewSize = mCachedViews.size(); if (cachedViewSize >= mViewCacheMax && cachedViewSize > 0) { // 缓存数量大于2的时候将最早进来的ViewHolder移除 recycleCachedViewAt(0); cachedViewSize--; } // ... mCachedViews.add(targetCacheIndex, holder); cached = true; } if (!cached) { addViewHolderToRecycledViewPool(holder, true); recycled = true; } } else { // ... } // ViewInfoStore 中移除 mViewInfoStore.removeViewHolder(holder); }
从上述的`Recycler#recycleViewHolderInternal`方法能够看出,`ViewHolder`会被优先加入`mCachedViews`,当`mCachedViews`数量大于2的时候,会调用`Recycler#recycleCachedViewAt`方法:
复制代码
void recycleCachedViewAt(int cachedViewIndex) { // ... ViewHolder viewHolder = mCachedViews.get(cachedViewIndex); // 添加进缓存池RecyclerPool addViewHolderToRecycledViewPool(viewHolder, true); // 从mCachedViews中移除 mCachedViews.remove(cachedViewIndex); }
由于`cachedViewIndex`是2,因此`mCachedViews`中`ViewHolder`数量为2的时候,会先添加到`mCachedViews`,而后从`mCachedViews`中移除先进来的`ViewHolder`添加进缓存池。
我在这里选取了一些经常使用的场景,整合出以下图片:

须要指明的是:
1. `mChangedScrap`实际并未参加真实的缓存过程,它的添加和移除`ViewHolder`都出如今`RecyclerView#dispatchLayoutStep1`方法中的`PreLayout(预布局)`过程当中。
2. 对于`RecyclerView`中已经显示并将继续展现的`ViewHolder`,重绘过程当中,会将`ViewHolder`以及其中的子`View`从`RecyclerView`移出,添加进`mAttachedScrap`中,并在后续的填充子`View`过程当中,从`mAttachedScrap`取出。
3. `mCachedViews`最多只能缓存两个`ViewHolder`,若是大于最大缓存数量,会将先进来的`ViewHolder`取出加入`RecycledViewPool`。
4. `RecycledViewPool`针对每种`viewType`的`ViewHolder`提供最大最大数量为5的缓存。
有了`Recycler`之后:

灰色的是小T同窗的手机屏幕,查看聊天记录的时候,`RecyclerView`不会每次都建立新的`ViewHolder`,也不会一次性将全部的`ViewHolder`都建好,减小了内存和时间的损耗,因此,小T同窗就能够流畅的查看和女朋友的上千条聊天记录了~
## 3、浅谈设计模式
阅读源码的过程当中,发现`RecyclerView`运用了不少设计模式。
看`Adapter`类这个名字,就能够看出它使用了**适配器模式**,由于涉及到将数据集转变成`RecyclerView`须要的子视图。除了适配器模式以外,`Adapter`中还使用**观察者模式**,这一点能够从`RecyclerView#setAdapter`方法中能够看出,设置适配器的时候,会对旧的`Adapter`取消注册监听器,接着对新的`Adapter`注册监听器,等到数据发生变化的时候,通知给观察者,观察者就能够在`RecyclerView`内愉快地删除或者新增子视图了。
接着,看`LayoutManager`这个类,`RecyclerView`将给`View`布局这个任务交给了抽象类`LayoutManager`,根据不一样需求,好比线性布局能够用`LinearLayoutManager`实现,网格布局能够用`GridLayoutManager`。应对同一个布局问题,`RecyclerView`使用了**策略模式**,给出了不一样的解决方案,`ItemAnimator`也是如此。
若是感兴趣的话,同窗们能够查看对应的源码。
## 4、总结
本文中,除了对`Recycler`进行深层次研究外,其余则点到为止,大体获得以下结论:

后续博客中,我将和你们一块儿学习`RecyclerView`中的其余部分。
这大概是我写的最难受的博客之一了,一是`RecyclerView`的源码很长,看着有点累;二是源码分析的博客确实不知道怎么写,还在持续探索中。哈哈~,终于写完了第一篇,本人水平有限,不免有误,欢迎指出哟~
若是你对本系列文章感兴趣:
> 第二篇:[《抽丝剥茧RecyclerView - LayoutManager》](https://juejin.cn/post/6844903924256735239)
参考文章:
>[《RecyclerView 源码解析》](https://juejin.cn/post/6844903459011969037#comment)
[《RecyclerView缓存原理,有图有真相》](https://juejin.cn/post/6844903661726859271)
[《RecyclerView缓存机制(咋复用?)》](https://juejin.cn/post/6844903778303344647)
[《RecyclerView动画源码浅析》](https://juejin.cn/post/6844903743352209421)
[《Understanding RecyclerView Components. Part -2》](https://android.jlelse.eu/understanding-recyclerview-components-part-2-1fd43001a98f)
复制代码