抽丝剥茧RecyclerView
系列文章的目的在于帮助Android开发者提升对RecyclerView的认知,本文是整个系列的第一章。android
RecyclerView
已经出来好久了,不少开发者对于RecyclerView
的使用早已信手拈来。以下就是一张使用网格布局的RecyclerView
: 设计模式
RecyclerView
这种明星控件的了解仅停留在使用的程度,显然是不可以让咱们成为高级工程师的。若是你看过
RecyclerView
包中的源码,那你应该和个人心情同样复杂,光一个
RecyclerView.class
文件的源码就多达13000行。
对于源码阅读方式,我很同意郭神在Glide源码分析中所说:缓存
抽丝剥茧、点到即止。应该认准一个功能点,而后去分析这个功能点是如何实现的。但只要去追寻主体的实现逻辑便可,千万不要试图去搞懂每一行代码都是什么意思,那样很容易会陷入到思惟黑洞当中,并且越陷越深。bash
因此,我在阅读RecyclerView
源码的时候先肯定好本身想好了解的功能点:app
阅读姿式 :我选择了版本为27.1.1
的RecyclerView
,不知道什么缘由,我点进28.0.0
版本的RecyclerView库
中查看RecyclerView.class
代码时,虽然类缩短至7000行,可是注释没了以及其余的问题,我不得不使用其余版本的RecyclerView
库。ide
想要深刻原理,没有什么是一遍调试解决不了的,若是有,那就是调试第二遍。函数
以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
各个部分的做用:布局
主要的类 | 做用 |
---|---|
LayoutManager |
负责RecyclerView 子View 的布局,经常使用的有LinearLayoutManager (线性布局),还有GridLayoutManager (网格布局)和StaggeredGridLayoutManager (瀑布布局)等。 |
Adapter |
负责将数据转变成视图,使用时须要继承该类。 |
ItemAnimator |
子视图动画,RecyclerView 有默认的子视图动画,也可自定义实现。 |
ItemDecoration |
分隔线,需自定义实现。 |
以上是咱们使用RecyclerView
的时候可以直观看到的部分,还有一个很重要可是不直接使用的类:post
主要的类 | 做用 |
---|---|
Recycler |
负责ViewHolder 的回收和提供。 |
RecyclerView
的源码那么多,咱们先按照使用时的路线进行分析。
一般,咱们会在布局文件中使用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
实例。
研究自定义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
设置完成之后就能够直奔主题了。
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
中讲解。
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
,这也就解决了咱们一开始提出的两个问题:
至于LayoutManager
和RecyclerView.ItemAnimator
更深层次的探讨,我将会在后面的博客中进行。
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
在三大工程流程中大概作了以下的事:
在上文中,咱们简要了解RecyclerView绘制的三大流程以及LayoutManager
和ItemAnimator
承担的任务。显然,咱们忽略了适配器Adapter
和缓存管理Recycler
,下面咱们就重点谈谈这两位。
上文中,咱们了解到在RecyclerView#dispatchLayoutStep2
方法中,给子View定位的任务交给了LayoutManager
:
mLayout.onLayoutChildren(mRecycler, mState);
复制代码
简要的介绍一下LayoutManger#onLayoutChildren
的工做内容:
RecyclerView
中还存在子View
,移除全部的子View
,将移除的ViewHolder
添加进Recycler
。Recycler
获取一个子View。RecyclerView
便可。虽然上面的内容很简单,可是LayoutManager
的实际工做内容要复杂的多,那么 Recycler
工做机制是怎样的呢?咱们来一探究竟。
先看组成部分:
缓存级别 | 参与对象 | 做用 |
---|---|---|
一级缓存 | mAttachedScrap 、mChangedScrap |
mChangedScrap 仅参与预布局,mAttachedScrap 存放还会被复用的ViewHolder |
二级缓存 | mCachedViews |
最多存放2个缓存ViewHolder |
三级缓存 | mViewCacheExtension |
需开发者自定义实现 |
四级缓存 | mRecyclerPool |
能够理解RecyclerPool 是(int,ArrayList<ViewHolder>) 的SparseArray ,键是viewType ,每一个viewType 最多能够存放5个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<Object> payloads) {
onBindViewHolder(holder, position);
}
public abstract void onBindViewHolder(VH holder, int position);
复制代码
成功见到咱们必须实现的Adapter#onBindViewHolder
方法,这些完成之后,子View
就会被交给LayoutManager
管理了。
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
在回收的过程当中,大概作了以下的事情:
RecyclerView
移除子视图。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
添加进缓存池。
我在这里选取了一些经常使用的场景,整合出以下图片:
mChangedScrap
实际并未参加真实的缓存过程,它的添加和移除ViewHolder
都出如今RecyclerView#dispatchLayoutStep1
方法中的PreLayout(预布局)
过程当中。RecyclerView
中已经显示并将继续展现的ViewHolder
,重绘过程当中,会将ViewHolder
以及其中的子View
从RecyclerView
移出,添加进mAttachedScrap
中,并在后续的填充子View
过程当中,从mAttachedScrap
取出。mCachedViews
最多只能缓存两个ViewHolder
,若是大于最大缓存数量,会将先进来的ViewHolder
取出加入RecycledViewPool
。RecycledViewPool
针对每种viewType
的ViewHolder
提供最大最大数量为5的缓存。有了Recycler
之后:
RecyclerView
不会每次都建立新的
ViewHolder
,也不会一次性将全部的
ViewHolder
都建好,减小了内存和时间的损耗,因此,小T同窗就能够流畅的查看和女朋友的上千条聊天记录了~
阅读源码的过程当中,发现RecyclerView
运用了不少设计模式。
看Adapter
类这个名字,就能够看出它使用了适配器模式,由于涉及到将数据集转变成RecyclerView
须要的子视图。除了适配器模式以外,Adapter
中还使用观察者模式,这一点能够从RecyclerView#setAdapter
方法中能够看出,设置适配器的时候,会对旧的Adapter
取消注册监听器,接着对新的Adapter
注册监听器,等到数据发生变化的时候,通知给观察者,观察者就能够在RecyclerView
内愉快地删除或者新增子视图了。
接着,看LayoutManager
这个类,RecyclerView
将给View
布局这个任务交给了抽象类LayoutManager
,根据不一样需求,好比线性布局能够用LinearLayoutManager
实现,网格布局能够用GridLayoutManager
。应对同一个布局问题,RecyclerView
使用了策略模式,给出了不一样的解决方案,ItemAnimator
也是如此。
若是感兴趣的话,同窗们能够查看对应的源码。
本文中,除了对Recycler
进行深层次研究外,其余则点到为止,大体获得以下结论:
RecyclerView
中的其余部分。
这大概是我写的最难受的博客之一了,一是RecyclerView
的源码很长,看着有点累;二是源码分析的博客确实不知道怎么写,还在持续探索中。哈哈~,终于写完了第一篇,本人水平有限,不免有误,欢迎指出哟~
若是你对本系列文章感兴趣:
参考文章:
《RecyclerView 源码解析》
《RecyclerView缓存原理,有图有真相》
《RecyclerView缓存机制(咋复用?)》
《RecyclerView动画源码浅析》
《Understanding RecyclerView Components. Part -2》