RecyclerView包含以下几个重要的组件:
1.LayoutManager: 测量和布局子View
2.Recycler: View的缓存、复用
3.ViewHolder: 对itemView及其元数据的封装
4.ItemAnimator: 动画
5.Adapter: 创建ViewHolder、绑定数据、通知数据变更
6.ItemDecoration: ItemView的装饰
7.SmoothScroller: 平滑滚动
8.ViewFlinger: 功能未知...
先看看最基本也最重要的Adapter。
RecyclerView.Adapter
在RecyclerView中,Adapter是其内部的一个抽象类,咱们最熟悉的两个方法: onCreateViewHolder 和 onBindViewHolder,分别在 createViewHolder 和 bindViewHolder 中被调用。
public final VH createViewHolder(ViewGroup parent, int viewType) {
TraceCompat.beginSection(TRACE_CREATE_VIEW_TAG);
final VH holder = onCreateViewHolder(parent, viewType);
holder.mItemViewType = viewType;
TraceCompat.endSection();
return holder;
}
这里直接调用咱们复写的onCreateViewHolder,传进去两个参数。
ViewGroup: 当前View绑定到Adapter的position后添加到的ViewGroup
ViewType: 当前View的类型(这个类型由咱们自己定义,有时候一个列表需要有各种奇形怪状的item,有方的,有圆的,都是不同的类型)
public final void bindViewHolder(VH holder, int position) {
holder.mPosition = position;
if (hasStableIds()) {
holder.mItemId = getItemId(position);
}
holder.setFlags(ViewHolder.FLAG_BOUND,
ViewHolder.FLAG_BOUND | ViewHolder.FLAG_UPDATE | ViewHolder.FLAG_INVALID
| ViewHolder.FLAG_ADAPTER_POSITION_UNKNOWN);
TraceCompat.beginSection(TRACE_BIND_VIEW_TAG);
onBindViewHolder(holder, position, holder.getUnmodifiedPayloads());
holder.clearPayload();
final ViewGroup.LayoutParams layoutParams = holder.itemView.getLayoutParams();
if (layoutParams instanceof RecyclerView.LayoutParams) {
((LayoutParams) layoutParams).mInsetsDirty = true;
}
TraceCompat.endSection();
}
这个也很简单,先记录了当前Holder的position和id(如果设了hasStableId,这个究竟是什么先按下不表,因为现在我还不知道...),然后设了Holder的Flag状态,标记为绑定状态,调用咱们的onBindViewHolder,置LayoutParams为Dirty(即数据已变更,需要重绘)。
除了上面两个方法,还有比较容易理解的 getItemCount 和 getItemId, getItemViewType, 这几个不提也罢。
onViewRecycled, 当被创建的一个view被复用的时候被调用。就是,LayoutManager认为这个View没有价值了,比如在屏幕上不可见,就会复用这个View并且调用这个方法,可以在这里对该View进行资源释放。
然后,还有一个诡异的setHasStableId,设这个为 true 可以在notifyDataChange的时候提升效率,至于为什么,要等到后面看看notify的逻辑才能知晓了。
最后还有一个重头戏,就是notify的一系列方法。notify的方法可以归结为两种类型,一种是列表结构发生了变化,一种是单个item发生变化。当只有单个item的数据更新时,列表的位置没有变化,此时的变化属于后者,notifyItemChanged、notifyItemRangeChanged属于此类。而当item位置发生改变,就是列表结构的变化了,notifyItemInserted、notifyItemRangeInserted、notifyItemMoved、notifyItemRemoved、notifyItemRangeRemoved以及notifyDataSetChange属于此类。注意,少调用notifyDataSetChange可以提升响应速度,因为这个方法会假设所有数据即位置信息都发生了变化,会重新绑定所有数据,比较耗时,也无法执行Item变化的默认动画。
下面要介绍一下Adapter中对观察者模式的应用,即View监听数据的变化。
在Adapter中有一个 Observable 全局变量,是 AdapterDataObservable 的实例。AdapterDataObservable 是一个标准的 Observable 实现,其中包含了notify的一系列方法,在方法中使用for循环通知Observer数据发生了变化。那么订阅动作发生在哪里呢?
来看看RecyclerView的setAdapter方法
public void setAdapter(Adapter adapter) {
// bail out if layout is frozen
setLayoutFrozen(false);
setAdapterInternal(adapter, false, true);
requestLayout();
}
关键在这个setAdapterInternal里头
private void setAdapterInternal(Adapter adapter, boolean compatibleWithPrevious,
boolean removeAndRecycleViews) {
if (mAdapter != null) {
mAdapter.unregisterAdapterDataObserver(mObserver);
mAdapter.onDetachedFromRecyclerView(this);
}
if (!compatibleWithPrevious || removeAndRecycleViews) {
removeAndRecycleViews();
}
mAdapterHelper.reset();
final Adapter oldAdapter = mAdapter;
mAdapter = adapter;
if (adapter != null) {
adapter.registerAdapterDataObserver(mObserver);
adapter.onAttachedToRecyclerView(this);
}
if (mLayout != null) {
mLayout.onAdapterChanged(oldAdapter, mAdapter);
}
mRecycler.onAdapterChanged(oldAdapter, mAdapter, compatibleWithPrevious);
mState.mStructureChanged = true;
markKnownViewsInvalid();
}
先忽略别的操作,可以看到,在这里,先取消注册了Observer,然后重新注册。Observer是 RecyclerViewDataObserver 的实现。
private class RecyclerViewDataObserver extends AdapterDataObserver {
RecyclerViewDataObserver() {
}
@Override
public void onChanged() {
assertNotInLayoutOrScroll(null);
mState.mStructureChanged = true;
setDataSetChangedAfterLayout();
if (!mAdapterHelper.hasPendingUpdates()) {
requestLayout();
}
}
@Override
public void onItemRangeChanged(int positionStart, int itemCount, Object payload) {
assertNotInLayoutOrScroll(null);
if (mAdapterHelper.onItemRangeChanged(positionStart, itemCount, payload)) {
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 (POST_UPDATES_ON_ANIMATION && mHasFixedSize && mIsAttached) {
ViewCompat.postOnAnimation(RecyclerView.this, mUpdateChildViewsRunnable);
} else {
mAdapterUpdateDuringMeasure = true;
requestLayout();
}
}
}
在这个实现中,每一种变换都会先检查当前没有在布局或者滚动过程中,然后调用AdapterHelper的相应方法,最后 triggerUpdateProcessor. 在这个方法里可以看到,满足三个条件会直接执行动画,不满足则需要重新布局。
POST_UPDATES_ON_ANIMATION: 当前动作是否执行动画,定义是SDK大于16.
mHasFixedSize: 这是一个RecyclerView可以设置的参数,如果所有Item的大小一致,则可以直接置为true。如果没有置true,则需要重新布局。
mIsAttached: 即RecyclerView是否attach到当前Window.
再看看这个执行动画的mUpdateChildViewsRunnable
/**
* Note: this Runnable is only ever posted if:
* 1) We've been through first layout
* 2) We know we have a fixed size (mHasFixedSize)
* 3) We're attached
*/
final Runnable mUpdateChildViewsRunnable = new Runnable() {
@Override
public void run() {
if (!mFirstLayoutComplete || isLayoutRequested()) {
// a layout request will happen, we should not do layout here.
return;
}
if (!mIsAttached) {
requestLayout();
// if we are not attached yet, mark us as requiring layout and skip
return;
}
if (mLayoutFrozen) {
mLayoutRequestEaten = true;
return; //we'll process updates when ice age ends.
}
consumePendingUpdateOperations();
}
};
注解和代码都很清楚,满足三个条件就会执行consumePendingUpdateOperations方法。这个方法的意思是执行当前那些被推迟执行的更新操作,在里面调到了AdapterHelper的方法。
这里又引出来一个AdapterHelper,定义里赫然写着:处理Adapter更新。
看看这个类的介绍,它为每一个adapter的数据变化创建一个UpdateOps,然后对他们进行预处理,决定哪些可以被推迟执行,哪些不可以。对于要求立即执行的UpdateOps,AdapterHelper会在第一次layout前根据上一个被推迟操作来对其进行更改。由于操作的顺序在这个过程中改变了,所以它也处理被推迟的UpdateOps.
即使操作被以不同的顺序转发给LayoutManager,但数据是保证绝对正确的。
那这些数据变更是怎么被转发给LayoutManager的呢?
这里关键是它的内部CallBack接口
/**
* Contract between AdapterHelper and RecyclerView.
*/
interface Callback {
RecyclerView.ViewHolder findViewHolder(int position);
void offsetPositionsForRemovingInvisible(int positionStart, int itemCount);
void offsetPositionsForRemovingLaidOutOrNewView(int positionStart, int itemCount);
void markViewHoldersUpdated(int positionStart, int itemCount, Object payloads);
void onDispatchFirstPass(UpdateOp updateOp);
void onDispatchSecondPass(UpdateOp updateOp);
void offsetPositionsForAdd(int positionStart, int itemCount);
void offsetPositionsForMove(int from, int to);
}
流程是这样的,consumePendingUpdateOperations 的描述是:滚动过程中的数据变更可能会导致Crash,滚动动作会假定没有数据变更。此方法消灭掉所有的延时变更来避免这个问题。
在这个方法中会调用AdapterHelper的preProcess预处理方法,preProcess会调用不同的apply方法(applyAdd, applyRemove等),调到dispatchAndUpdateViewHolders,最终调用到RecyclerView中实现的Callback的dispatchUpdate方法来分发更新事件,在这里调用LayoutManager的不同方法,对UI做出更改。
void dispatchUpdate(AdapterHelper.UpdateOp op) {
switch (op.cmd) {
case AdapterHelper.UpdateOp.ADD:
mLayout.onItemsAdded(RecyclerView.this, op.positionStart, op.itemCount);
break;
case AdapterHelper.UpdateOp.REMOVE:
mLayout.onItemsRemoved(RecyclerView.this, op.positionStart, op.itemCount);
break;
case AdapterHelper.UpdateOp.UPDATE:
mLayout.onItemsUpdated(RecyclerView.this, op.positionStart, op.itemCount,
op.payload);
break;
case AdapterHelper.UpdateOp.MOVE:
mLayout.onItemsMoved(RecyclerView.this, op.positionStart, op.itemCount, 1);
break;
}
}
那么以上就是RecyclerView使用观察者模式,在数据变更时通知UI更新的流程。
OK,趁热打铁(其实已经过了一晚上了...),咱们来看看ViewHolder吧(由于昨夜精神太好所以就Solo了一下,挑个简单的提提神)。
ViewHolder
ViewHolder是一个对itemView、item数据、item类型的封装。
同样的,ViewHolder也是RecyclerView的一个内部抽象类。先看看它为自己定义的状态:
FLAG_BOUND——ViewHolder已经绑定到某个位置,mPosition、mItemId、mItemViewType都有效
FLAG_UPDATE——ViewHolder绑定的View对应的数据过时需要重新绑定,mPosition、mItemId还是一致的
FLAG_INVALID——ViewHolder绑定的View对应的数据无效,需要完全重新绑定不同的数据
FLAG_REMOVED——ViewHolder对应的数据已经从数据集移除
FLAG_NOT_RECYCLABLE——ViewHolder不能复用
FLAG_RETURNED_FROM_SCRAP——这个状态的ViewHolder会加到scrap list被复用。
FLAG_CHANGED——ViewHolder内容发生变化,通常用于表明有ItemAnimator动画
FLAG_IGNORE——ViewHolder完全由LayoutManager管理,不能复用
FLAG_TMP_DETACHED——ViewHolder从父RecyclerView临时分离的标志,便于后续移除或添加回来
FLAG_ADAPTER_POSITION_UNKNOWN——ViewHolder不知道对应的Adapter的位置,直到绑定到一个新位置
FLAG_ADAPTER_FULLUPDATE——方法addChangePayload(null)调用时设置
因为牵扯到View的复用,ViewHolder的状态是很复杂的,后面在追到不同地方的时候会慢慢看到这些状态的实际应用。
在这里头,position是个搞脑子的东西。在之前版本的RecyclerView中,ViewHolder只有一个getPosition方法,后来被Deprecate了,注释道:这个方法很模糊,position在某些情况下是会冲突的,然后给了两个新方法,getLayoutPosition和getAdapterPosition. 注释还说,RecyclerView在下一次布局绘制前不会处理任何Adapter的更新,这就造成了一个问题:比如现在有一个Item的position是0,此时调用notifyItemInserted(0),Adapter实际已经更新了,getAdapterPosition返回1,但是RecyclerView还没有进行重绘,那么它不处理任何接收到的更新,此时getLayoutPosition还是返回0. 就造成了Adapter的position和用户实际看到的position不匹配的时间窗口,这个窗口注释声称小于16ms,那么这里如果调了错误的方法就可能出bug. 因为LayoutManager是管理UI的,所以应该调用的是getLayoutPosition,必须保证对UI的处理与用户看到的一致。而对用户事件的处理则应该调getAdapterPosition,不然就可能出现用户明明点的是第0位的item,第1位的item响应了事件的bug.
ViewHolder值得说的也就这了...
下面看看LayoutManager吧,本来想直接看Recycler的缓存机制的,考虑到这个缓存机制跟LayoutManager息息相关,所以咱们一个一个来,把LayoutManager解决先。
LayoutManager
LayoutManager应该是RecyclerView中最复杂的组件了,作为一个抽象内部类,洋洋洒洒三千行代码...如开篇所说,LayoutManager处理RecyclerView UI相关的功能,比如测量布局items、滚动页面等等。
幸运的是,v7包默认已经帮咱们实现了三个LayoutManager,看着具体实现的话,追源码就不会那么累了。这三个分别是 LinearLayoutManager, GridLayoutManager, StaggeredGridLayoutManager. 分别实现了线性布局、table布局和一个不知道什么鬼东西的布局。Stagger本身有动态的意思,所以...动态table?...
先不扯实现了,来看看LayoutManager这个抽象内部类吧。
/**
* A <code>LayoutManager</code> is responsible for measuring and positioning item views
* within a <code>RecyclerView</code> as well as determining the policy for when to recycle
* item views that are no longer visible to the user. By changing the <code>LayoutManager</code>
* a <code>RecyclerView</code> can be used to implement a standard vertically scrolling list,
* a uniform grid, staggered grids, horizontally scrolling collections and more. Several stock
* layout managers are provided for general use.
*/
照顾一下英语不好的童鞋: LayoutManager用于测量和摆放itemViews,同时负责决定在什么时间点可以复用那些不被用户看到的itemViews. 更改LayoutManager可以实现竖的、横的、grid的等等各种奇形怪状的列表。
咱们都知道,RecyclerView也是一个ViewGroup(废话),它在摆放子View的时候会有测量和布局的流程,而这两个操作都已经托付给LayoutManager来完成了,那么肯定会有这两个事件的传递过程。
先来看看测量
@Override
protected void onMeasure(int widthSpec, int heightSpec) {
if (mLayout == null) {
defaultOnMeasure(widthSpec, heightSpec);
return;
}
if (mLayout.mAutoMeasure) {
final int widthMode = MeasureSpec.getMode(widthSpec);
final int heightMode = MeasureSpec.getMode(heightSpec);
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) {
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 {
if (mHasFixedSize) {
mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);
return;
}
// custom onMeasure
if (mAdapterUpdateDuringMeasure) {
eatRequestLayout();
onEnterLayoutOrScroll();
processAdapterUpdatesAndSetAnimationFlags();
onExitLayoutOrScroll();
if (mState.mRunPredictiveAnimations) {
mState.mInPreLayout = true;
} else {
// consume remaining updates to provide a consistent state with the layout pass.
mAdapterHelper.consumeUpdatesInOnePass();
mState.mInPreLayout = false;
}
mAdapterUpdateDuringMeasure = false;
resumeRequestLayout(false);
}
if (mAdapter != null) {
mState.mItemCount = mAdapter.getItemCount();
} else {
mState.mItemCount = 0;
}
eatRequestLayout();
mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);
resumeRequestLayout(false);
mState.mInPreLayout = false; // clear
}
}
看上去好像干了很多事情,我们就以这个方法为主干,分析测量流程。
开始之前先看一下mState,看名字就猜到这是用来记录状态的。注释里把它称为data bus,可见除了记录状态,它还有记录信息的作用。RecyclerView各个组件间信息的传递也靠它了。里头有一个mLayoutStep,这个值有三个状态,分别是STEP_START、STEP_LAYOUT、STEP_ANIMATIONS,mLayoutStep初始值为STEP_START.
首先判了一下空,mLayout就是LayoutManager的实例。如果为空,使用默认的测量机制。这个不管。
然后接触到第一个LayoutManager的可设参数,AutoMeasure,也就是自动测量。 注释写了一大段中学生作文,总结一下就是:这个属性决定了RecyclerView如何进行测量。如果开启AutoMeasure,那么RecyclerView将支持WRAP_CONTENT属性,以包裹内容为终极目标来执行测量。如果不开启,就要复写LayoutManager的onMeasure方法来自定义测量方案。框架提供的三个LayoutManager实现都是用的这个自动测量机制。
关于这个自动测量机制是如何实现的,后面会专门分一块出来细读,现在咱还是紧跟步伐,假设我们现在有一个宽高固定的RecyclerView,默认进入自动测量,然后调了LayoutManager的onMeasure. 咱们以LinearLayoutManager为例,它并未实现onMeasure,在抽象类中onMeasure调用了defaultOnMeasure,这个方法仅仅把宽度加上paddings,高度加上paddings,然后就setMeasuredDimension了,非常直截了当。
然后由于宽高确定,所以直接返回。onLayout的调用紧随其后。
onLayout操作直接调给了dispatchLayout()方法。
void dispatchLayout() {
if (mAdapter == null) {
Log.e(TAG, "No adapter attached; skipping layout");
// leave the state in START
return;
}
if (mLayout == null) {
Log.e(TAG, "No layout manager attached; skipping layout");
// leave the state in START
return;
}
mState.mIsMeasuring = false;
if (mState.mLayoutStep == State.STEP_START) {
dispatchLayoutStep1();
mLayout.setExactMeasureSpecsFrom(this);
dispatchLayoutStep2();
} else if (mAdapterHelper.hasUpdates() || mLayout.getWidth() != getWidth() ||
mLayout.getHeight() != getHeight()) {
// First 2 steps are done in onMeasure but looks like we have to run again due to
// changed size.
mLayout.setExactMeasureSpecsFrom(this);
dispatchLayoutStep2();
} else {
// always make sure we sync them (to ensure mode is exact)
mLayout.setExactMeasureSpecsFrom(this);
}
dispatchLayoutStep3();
}
此方法第一次被调用时会依次执行dispatchLayoutStep1() 、dispatchLayoutStep2() 和 dispatchLayoutStep3().
好,下面来挨个儿仔细看看layout的这三个步骤。
dispatchLayoutStep1
注释:
1.处理adapter的更新
2.确定要执行的动画
3.保存当前views的信息
4.如果必要,运行可预测布局,并保存其信息
private void dispatchLayoutStep1() {
mState.assertLayoutStep(State.STEP_START);
mState.mIsMeasuring = false;
eatRequestLayout();
mViewInfoStore.clear();
onEnterLayoutOrScroll();
processAdapterUpdatesAndSetAnimationFlags();
saveFocusInfo();
mState.mTrackOldChangeHolders = mState.mRunSimpleAnimations && mItemsChanged;
mItemsAddedOrRemoved = mItemsChanged = false;
mState.mInPreLayout = mState.mRunPredictiveAnimations;
mState.mItemCount = mAdapter.getItemCount();
findMinMaxChildLayoutPositions(mMinMaxLayoutPositions);
if (mState.mRunSimpleAnimations) {
//...
}
if (mState.mRunPredictiveAnimations) {
//...
} else {
clearOldPositions();
}
onExitLayoutOrScroll();
resumeRequestLayout(false);
mState.mLayoutStep = State.STEP_LAYOUT;
}
在此方法中置mState.mIsMeasuring = false,然后处理adapter的更新。
来看看处理adapter更新的部分。
/**
* Consumes adapter updates and calculates which type of animations we want to run.
* Called in onMeasure and dispatchLayout.
* <p>
* This method may process only the pre-layout state of updates or all of them.
*/
private void processAdapterUpdatesAndSetAnimationFlags() {
if (mDataSetHasChangedAfterLayout) {
// Processing these items have no value since data set changed unexpectedly.
// Instead, we just reset it.
mAdapterHelper.reset();
mLayout.onItemsChanged(this);
}
// simple animations are a subset of advanced animations (which will cause a
// pre-layout step)
// If layout supports predictive animations, pre-process to decide if we want to run them
if (predictiveItemAnimationsEnabled()) {
mAdapterHelper.preProcess();
} else {
mAdapterHelper.consumeUpdatesInOnePass();
}
boolean animationTypeSupported = mItemsAddedOrRemoved || mItemsChanged;
mState.mRunSimpleAnimations = mFirstLayoutComplete
&& mItemAnimator != null
&& (mDataSetHasChangedAfterLayout
|| animationTypeSupported
|| mLayout.mRequestedSimpleAnimations)
&& (!mDataSetHasChangedAfterLayout
|| mAdapter.hasStableIds());
mState.mRunPredictiveAnimations = mState.mRunSimpleAnimations
&& animationTypeSupported
&& !mDataSetHasChangedAfterLayout
&& predictiveItemAnimationsEnabled();
}
上来就是一个判断,问数据是否已经更改,如果已更改,那么处理这些items就没有意义了,直接reset. mDataSetHasChangedAfterLayout这个参数会在两种情况下被置为true,分别是更换adapter的时候,以及调用notifyDataSetChange的时候,这也解释了上面讲的调用这个方法会导致性能损失的现象。
然后判断可预期item动画是否开启,这里再展开讲一下可预期动画。这个要返回true要满足两个条件,ItemAnimator不为空,且LayoutManager支持可预期动画。LayoutManager默认返回false,即不支持。注意,如果RecyclerView的ItemAnimator不为空,LayoutManager的supportsPredictiveItemAnimations返回false,那么会自动开启simple item animations,添加或移除views只进行简单的淡入淡出动画。如果ItemAnimator不为空且supportsPredictiveItemAnimations返回true,那么onLayoutChildren(Recycler, State)会被调用两次,目的是为了记录需要的信息来更智能地预测什么样的动画需要怎样被执行。
回过头来,如果开启了可预期item动画,就会执行adapterHelper的preProcess预处理方法,上面分析Adapter的时候已经讲过,这个方法最终会调到RecyclerView实现的AdapterHelper的Callback接口的分发更新方法,最终执行更新动画。
如果未开启可预期动画,则调用adapterHelper的consumeUpdatesInOnePass,不对更新操作进行预处理,直接回调到RecyclerView里的更新事件,传递给LayoutManager.
然后就是计算需要执行的动画了,这里要看一下两个状态,mRunSimpleAnimations 和 mRunPredictiveAnimations。
mState.mRunSimpleAnimations = mFirstLayoutComplete
&& mItemAnimator != null
&& (mDataSetHasChangedAfterLayout
|| animationTypeSupported
|| mLayout.mRequestedSimpleAnimations)
&& (!mDataSetHasChangedAfterLayout
|| mAdapter.hasStableIds());
mState.mRunPredictiveAnimations = mState.mRunSimpleAnimations
&& animationTypeSupported
&& !mDataSetHasChangedAfterLayout
&& predictiveItemAnimationsEnabled();
可以看到,mRunSimpleAnimations在满足以下条件时为true:
1.mFirstLayoutComplete为true(onLayout第一次执行完后被置为true)
2.mItemAnimator不为空
3.Layout后数据发生了变化 或 有item被移除或添加 或 LayoutManager请求执行simple animations
4.Layout后数据不发生变化 或 mAdapter有稳定的ID
而运行预期动画mRunPredictiveAnimations则在以下条件被满足时返回true:
1.mRunSimpleAnimations为true
2.有item添加或移除
3.Layout后数据未发生变化
4.预期Item动画被开启
回到step1,在记录了一些状态后,记录了adapter当前items的count,置状态mInPreLayout为执行预期动画的值,第一次调用为false,记录第一个和最后一个子View的位置信息。然后判断是否运行simple animations 和 预期item动画。由于第一次Layout尚未完成,所以不会执行。
step1的最后把State的mLayoutStep置为了STEP_LAYOUT.
layoutStep1执行完毕后,调用了mLayout.setExactMeasureSpecsFrom(this);
,把当前RecyclerView的绝对大小告知了LayoutManager. 然后马上调用step2.
dispatchLayoutStep2:
注释:
布局第二步对views进行真正的最终布局,确定最终状态。
如果有必要会被调用多次。
/**
* 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() {
eatRequestLayout();
onEnterLayoutOrScroll();
mState.assertLayoutStep(State.STEP_LAYOUT | State.STEP_ANIMATIONS);
mAdapterHelper.consumeUpdatesInOnePass();
mState.mItemCount = mAdapter.getItemCount();
mState.mDeletedInvisibleItemCountSincePreviousLayout = 0;
// Step 2: Run layout
mState.mInPreLayout = false;
mLayout.onLayoutChildren(mRecycler, mState);
mState.mStructureChanged = false;
mPendingSavedState = null;
// onLayoutChildren may have caused client code to disable item animations; re-check
mState.mRunSimpleAnimations = mState.mRunSimpleAnimations && mItemAnimator != null;
mState.mLayoutStep = State.STEP_ANIMATIONS;
onExitLayoutOrScroll();
resumeRequestLayout(false);
}
在这里首先调了mAdapterHelper的consumeUpdatesInOnePass,此方法跳过了预处理的阶段,直接对adapter的更新进行处理。然后再次记录了adapter的itemCount. 再然后,置状态mInPreLayout标识为false,调用LayoutManager的onLayoutChildren(mRecycler, mState)方法执行真正的Layout.
LayoutManager完成了布局子View后,dispatchLayoutStep2置状态mLayoutStep为STEP_ANIMATIONS. 至此,LayoutStep2执行完毕。
dispatchLayoutStep3
注释:
Layout的最后一步,在这里保存views的信息用于动画。
触发动画,并且做好善后工作。
private void dispatchLayoutStep3() {
mState.assertLayoutStep(State.STEP_ANIMATIONS);
eatRequestLayout();
onEnterLayoutOrScroll();
mState.mLayoutStep = State.STEP_START;
if (mState.mRunSimpleAnimations) {
//...
}
mLayout.removeAndRecycleScrapInt(mRecycler);
mState.mPreviousLayoutItemCount = mState.mItemCount;
mDataSetHasChangedAfterLayout = false;
mState.mRunSimpleAnimations = false;
mState.mRunPredictiveAnimations = false;
mLayout.mRequestedSimpleAnimations = false;
if (mRecycler.mChangedScrap != null) {
mRecycler.mChangedScrap.clear();
}
if (mLayout.mPrefetchMaxObservedInInitialPrefetch) {
// Initial prefetch has expanded cache, so reset until next prefetch.
// This prevents initial prefetches from expanding the cache permanently.
mLayout.mPrefetchMaxCountObserved = 0;
mLayout.mPrefetchMaxObservedInInitialPrefetch = false;
mRecycler.updateViewCacheSize();
}
mLayout.onLayoutCompleted(mState);
onExitLayoutOrScroll();
resumeRequestLayout(false);
mViewInfoStore.clear();
if (didChildRangeChange(mMinMaxLayoutPositions[0], mMinMaxLayoutPositions[1])) {
dispatchOnScrolled(0, 0);
}
recoverFocusFromState();
resetFocusInfo();
}
挑重要的看。置mLayoutStep为STEP_START, 因为mRunSimpleAnimation依然为false,所以不执行判断内的代码。然后reset了一堆状态,记录当前itemCount为mPreviousLayoutItemCount. 最后,调用了LayoutManager的onLayoutCompleted,通知LayoutManager当前已经完成了Layout操作。
至此,Step3执行完毕。
以上就是第一次onLayout被调用的执行流程。
那么如果是已经完成了布局的RecyclerView,调用notifyItemRemoved()移除一个item时又是怎么走流程的呢?因为一个item被移除了,预期它要执行一个淡出动画,然后后面的Item上移这样一个简单的动作。
我们从notifyItemRemoved开始追,走一遍数据变更的完整流程。
触发Item移除的操作后,首先跟到notifyItemRemoved方法内部。
public final void notifyItemRemoved(int position) {
mObservable.notifyItemRangeRemoved(position, 1);
}
再到Observable看看:
public void notifyItemRangeRemoved(int positionStart, int itemCount) {
for (int i = mObservers.size() - 1; i >= 0; i--) {
mObservers.get(i).onItemRangeRemoved(positionStart, itemCount);
}
}
跟预期是一致的,通知观察者当前数据发生了变更。
@Override
public void onItemRangeRemoved(int positionStart, int itemCount) {
assertNotInLayoutOrScroll(null);
if (mAdapterHelper.onItemRangeRemoved(positionStart, itemCount)) {
triggerUpdateProcessor();
}
}
void triggerUpdateProcessor() {
if (POST_UPDATES_ON_ANIMATION && mHasFixedSize && mIsAttached) {
ViewCompat.postOnAnimation(RecyclerView.this, mUpdateChildViewsRunnable);
} else {
mAdapterUpdateDuringMeasure = true;
requestLayout();
}
}
由于我设置了setHasFixedSize为true,所以直接运行mUpdateChildViewsRunnable.
/**
* Note: this Runnable is only ever posted if:
* 1) We've been through first layout
* 2) We know we have a fixed size (mHasFixedSize)
* 3) We're attached
*/
final Runnable mUpdateChildViewsRunnable = new Runnable() {
@Override
public void run() {
if (!mFirstLayoutComplete || isLayoutRequested()) {
// a layout request will happen, we should not do layout here.
return;
}
if (!mIsAttached) {
requestLayout();
// if we are not attached yet, mark us as requiring layout and skip
return;
}
if (mLayoutFrozen) {
mLayoutRequestEaten = true;
return; //we'll process updates when ice age ends.
}
consumePendingUpdateOperations();
}
};
void consumePendingUpdateOperations() {
if (!mFirstLayoutComplete || mDataSetHasChangedAfterLayout) {
TraceCompat.beginSection(TRACE_ON_DATA_SET_CHANGE_LAYOUT_TAG);
dispatchLayout();
TraceCompat.endSection();
return;
}
if (!mAdapterHelper.hasPendingUpdates()) {
return;
}
// if it is only an item change (no add-remove-notifyDataSetChanged) we can check if any
// of the visible items is affected and if not, just ignore the change.
if (mAdapterHelper.hasAnyUpdateTypes(AdapterHelper.UpdateOp.UPDATE) && !mAdapterHelper
.hasAnyUpdateTypes(AdapterHelper.UpdateOp.ADD | AdapterHelper.UpdateOp.REMOVE
| AdapterHelper.UpdateOp.MOVE)) {
TraceCompat.beginSection(TRACE_HANDLE_ADAPTER_UPDATES_TAG);
eatRequestLayout();
onEnterLayoutOrScroll();
mAdapterHelper.preProcess();
if (!mLayoutRequestEaten) {
if (hasUpdatedView()) {
dispatchLayout();
} else {
// no need to layout, clean state
mAdapterHelper.consumePostponedUpdates();
}
}
resumeRequestLayout(true);
onExitLayoutOrScroll();
TraceCompat.endSection();
} else if (mAdapterHelper.hasPendingUpdates()) {
TraceCompat.beginSection(TRACE_ON_DATA_SET_CHANGE_LAYOUT_TAG);
dispatchLayout();
TraceCompat.endSection();
}
在这里会直接调用dispatchLayout,然后走布局三部曲。注意,mRunSimpleAnimations这个参数的条件已经满足了(如果忘了就到上面去看看需要哪些条件,懒得看就假装它被满足了吧),之前走第一遍流程的时候我们略过了跟它相关的代码,这次来看看。
dispatchLayoutStep1
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) {
final ViewHolder holder = getChildViewHolderInt(mChildHelper.getChildAt(i));
if (holder.shouldIgnore() || (holder.isInvalid() && !mAdapter.hasStableIds())) {
continue;
}
final ItemHolderInfo animationInfo = mItemAnimator
.recordPreLayoutInformation(mState, holder,
ItemAnimator.buildAdapterChangeFlagsForAnimations(holder),
holder.getUnmodifiedPayloads());
mViewInfoStore.addToPreLayout(holder, animationInfo);
if (mState.mTrackOldChangeHolders && holder.isUpdated() && !holder.isRemoved()
&& !holder.shouldIgnore() && !holder.isInvalid()) {
long key = getChangedHolderKey(holder);
// This is NOT the only place where a ViewHolder is added to old change holders
// list. There is another case where:
// * A VH is currently hidden but not deleted
// * The hidden item is changed in the adapter
// * Layout manager decides to layout the item in the pre-Layout pass (step1)
// When this case is detected, RV will un-hide that view and add to the old
// change holders list.
mViewInfoStore.addToOldChangeHolders(key, holder);
}
}
}
直接就是一个for循环,遍历当前所有可见的子View的ViewHolder,注意,是可---见---的view的ViewHolder. 这个可见View的count来自于ChildHelper,来看看ChildHelper的getChildCount:
int getChildCount() {
return mCallback.getChildCount() - mHiddenViews.size();
}
这个Callback实现当然是在RecyclerView里头,这里返回的是RecyclerView的子View减去隐藏view的数量。看好了,RecyclerView的childCount并不是所有数据Item count,而是当前RecyclerView可见的Views的数量。比如当前,我的主页只可见一个item,那么在这个时间点,RecyclerView的childCount就为1. 这就是为什么如果要获取列表数量,要调用Adapter的getChildCount,不能调RecyclerView的getChildCount,因为后者是一个随时在变化的动态值。
我的Demo中只有一个item可见,总共四个items. 所以只走了一次循环,把这个holder添加到了preLayoutList,就出去了。
mRunPredictiveAnimations同样满足条件。
if (mState.mRunPredictiveAnimations) {
// 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.
// Save old positions so that LayoutManager can run its mapping logic.
saveOldPositions();
final boolean didStructureChange = mState.mStructureChanged;
mState.mStructureChanged = false;
// temporarily disable flag because we are asking for previous layout
mLayout.onLayoutChildren(mRecycler, mState);
mState.mStructureChanged = didStructureChange;
for (int i = 0; i < mChildHelper.getChildCount(); ++i) {
final View child = mChildHelper.getChildAt(i);
final ViewHolder viewHolder = getChildViewHolderInt(child);
if (viewHolder.shouldIgnore()) {
continue;
}
if (!mViewInfoStore.isInPreLayout(viewHolder)) {
int flags = ItemAnimator.buildAdapterChangeFlagsForAnimations(viewHolder);
boolean wasHidden = viewHolder
.hasAnyOfTheFlags(ViewHolder.FLAG_BOUNCED_FROM_HIDDEN_LIST);
if (!wasHidden) {
flags |= ItemAnimator.FLAG_APPEARED_IN_PRE_LAYOUT;
}
final ItemHolderInfo animationInfo = mItemAnimator.recordPreLayoutInformation(
mState, viewHolder, flags, viewHolder.getUnmodifiedPayloads());
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();
}
注解都已经说了,运行preLayout(预布局),会使用items的旧的positions. LayoutManager应该Layout所有的东西,包括已经被移除的items.
这里也是一个循环,在循环前调用了LayoutManager的onLayout方法执行了一次布局,在布局的时候动了手脚。在LinearLayoutManager的实现中,偷偷地根据ViewHolder的标记位向RecyclerView添加了childView.
if (layoutState.mScrapList == null) {
if (mShouldReverseLayout == (layoutState.mLayoutDirection
== LayoutState.LAYOUT_START)) {
addView(view);
} else {
addView(view, 0);
}
}
这个addView调用的是抽象父类的addView方法。在抽象父类addView的实现中有这么一句:
mChildHelper.addView(child, index, false);
ChildHelper的addView:
mCallback.addView(child, offset);
RecyclerView中对Callback的实现:
@Override
public void addView(View child, int index) {
if (VERBOSE_TRACING) {
TraceCompat.beginSection("RV addView");
}
RecyclerView.this.addView(child, index);
if (VERBOSE_TRACING) {
TraceCompat.endSection();
}
dispatchChildAttached(child);
}
是他是他就是他,就是在这里把子View添加到RecyclerView当中的。
void dispatchChildAttached(View child) {
final ViewHolder viewHolder = getChildViewHolderInt(child);
onChildAttachedToWindow(child);
if (mAdapter != null && viewHolder != null) {
mAdapter.onViewAttachedToWindow(viewHolder);
}
if (mOnChildAttachStateListeners != null) {
final int cnt = mOnChildAttachStateListeners.size();
for (int i = cnt - 1; i >= 0; i--) {
mOnChildAttachStateListeners.get(i).onChildViewAttachedToWindow(child);
}
}
}
哈,看见熟面孔了么,onChildAttachedToWindow就是在这儿调用的。
好,跑题有点远。刚刚说到,Step1的预期布局判断的for循环前调用了LayoutManager的onLayout,在这里面向RecyclerView添加了即将要显示的子View. 这个循环会进行两次,拿到的第一个ViewHolder是上一次已经被加到PreLayout的第0位View,第二个ViewHolder是新添加的,并没有被hide,所以调的是addToAppearedInPreLayoutHolders.
这里打断一下,需要看一下这些holders都被塞到什么地方了。这里有一个ViewInfoStore类,注释说,这个类抽象了所有运行动画所需的View信息。在这里维护了两个集合,一个mLayoutHolderMap,用于存储那些即将执行动画的holders和它们相对应的信息的映射,另一个mOldChangedHolders,存储已存在的发生了改变的ViewHolder.
来看看Step1调用的是什么方法。在第一个mRunSimpleAnimation的判断中,调用的是addToPreLayout和addToOldChangeHolders两个方法。
void addToPreLayout(ViewHolder holder, ItemHolderInfo info) {
InfoRecord record = mLayoutHolderMap.get(holder);
if (record == null) {
record = InfoRecord.obtain();
mLayoutHolderMap.put(holder, record);
}
record.preInfo = info;
record.flags |= FLAG_PRE;
}
将第0位item加到了mLayoutHolderMap中,并记录标记为FLAG_PRE,代表的应该是当前holder即将执行动画。
void addToOldChangeHolders(long key, ViewHolder holder) {
mOldChangedHolders.put(key, holder);
}
这个没什么可说的。来看看mRunPredictiveAnimation判断的代码,调用的是addToAppearedInPreLayoutHolders.
void addToAppearedInPreLayoutHolders(ViewHolder holder, ItemHolderInfo info) {
InfoRecord record = mLayoutHolderMap.get(holder);
if (record == null) {
record = InfoRecord.obtain();
mLayoutHolderMap.put(holder, record);
}
record.flags |= FLAG_APPEAR;
record.preInfo = info;
}
这里也是存入了mLayoutHolderMap集合,但是置标记为FLAG_APPEAR,意思应该是即将执行出现的动画。
好了,对于数据变更时Step1所执行的操作应该了然于心了,Step2实质上只是又进行了一次布局。直接看Step3.
if (mState.mRunSimpleAnimations) {
// Step 3: Find out where things are now, and process change animations.
// traverse list in reverse because we may call animateChange in the loop which may
// remove the target view holder.
for (int i = mChildHelper.getChildCount() - 1; i >= 0; i--) {
ViewHolder holder = getChildViewHolderInt(mChildHelper.getChildAt(i));
if (holder.shouldIgnore()) {
continue;
}
long key = getChangedHolderKey(holder);
final ItemHolderInfo animationInfo = mItemAnimator
.recordPostLayoutInformation(mState, holder);
ViewHolder oldChangeViewHolder = mViewInfoStore.getFromOldChangeHolders(key);
if (oldChangeViewHolder != null && !oldChangeViewHolder.shouldIgnore()) {
// run a change animation
// If an Item is CHANGED but the updated version is disappearing, it creates
// a conflicting case.
// Since a view that is marked as disappearing is likely to be going out of
// bounds, we run a change animation. Both views will be cleaned automatically
// once their animations finish.
// On the other hand, if it is the same view holder instance, we run a
// disappearing animation instead because we are not going to rebind the updated
// VH unless it is enforced by the layout manager.
final boolean oldDisappearing = mViewInfoStore.isDisappearing(
oldChangeViewHolder);
final boolean newDisappearing = mViewInfoStore.isDisappearing(holder);
if (oldDisappearing && oldChangeViewHolder == holder) {
// run disappear animation instead of change
mViewInfoStore.addToPostLayout(holder, animationInfo);
} else {
final ItemHolderInfo preInfo = mViewInfoStore.popFromPreLayout(
oldChangeViewHolder);
// we add and remove so that any post info is merged.
mViewInfoStore.addToPostLayout(holder, animationInfo);
ItemHolderInfo postInfo = mViewInfoStore.popFromPostLayout(holder);
if (preInfo == null) {
handleMissingPreInfoForChangeError(key, holder, oldChangeViewHolder);
} else {
animateChange(oldChangeViewHolder, holder, preInfo, postInfo,
oldDisappearing, newDisappearing);
}
}
} else {
mViewInfoStore.addToPostLayout(holder, animationInfo);
}
}
// Step 4: Process view info lists and trigger animations
mViewInfoStore.process(mViewInfoProcessCallback);
}
这里先尝试从ViewInfoStore中取出保存的这个Holder的OldHolder,由于现在布局中存在的仅仅是第1位holder,而我们之前加到oldChangeHolders的只有第0位,所以拿到的是空,直接到addToPostLayout. 这个方法跟上面的几个类似,添加到mLayoutHolderMap,置标记为FLAG_POST. 然后执行mViewInfoProcessCallback.
process方法根据flag判断调用callback的哪个具体方法。这里首先调用的是processDisappeared, 执行消失,在这里调用animateDisappearance.
void animateDisappearance(@NonNull ViewHolder holder,
@NonNull ItemHolderInfo preLayoutInfo, @Nullable ItemHolderInfo postLayoutInfo) {
addAnimatingView(holder);
holder.setIsRecyclable(false);
if (mItemAnimator.animateDisappearance(holder, preLayoutInfo, postLayoutInfo)) {
postAnimationRunner();
}
}
addAnimationView:
private void addAnimatingView(ViewHolder viewHolder) {
final View view = viewHolder.itemView;
final boolean alreadyParented = view.getParent() == this;
mRecycler.unscrapView(getChildViewHolder(view));
if (viewHolder.isTmpDetached()) {
// re-attach
mChildHelper.attachViewToParent(view, -1, view.getLayoutParams(), true);
} else if(!alreadyParented) {
mChildHelper.addView(view, true);
} else {
mChildHelper.hide(view);
}
}
呦,还良心地讲解了这个机制是如何执行的,好好好,这就可以一边看着代码一边看着注释兑着灌了。
首先,RecyclerView的onMeasure被调用,如果MeasureSpec为EXACT,则不进行测量,直接返回(宽高已经被订好了)。否则,开始在onMeasure中处理布局流程。它会处理所有的待处理adapter更新,并决定是不是要进行预布局。如果要进行预布局,会将state.preLayout()置为true,然后调用onLayoutChildren(Recycler, State). 此时,getWidth与getHeight依然返回上一次layout的结果。
预处理完毕后,会设state.preLayout为false,state.isMeasuring为true,此时,LayoutManager就可以通过getHeight、getHeightMode来获取测量的specs了。
layout计算完后,RecyclerView为子view们计算边界盒(加上padding的大小),设置测量过的height和width. LayoutManager可以通过复写setMeasuredDimension(Rect, int, int)来选择不同的值。比如,GridLayoutManager复写这个值来处理三列布局时单排显示两个items的width计算。
此之后onMeasure的所有调用都会把状态置为isMeasuring. RecyclerView管理view的增删改操作,LayoutManager啥都不用管,只要把每个onLayoutChildren调用当做最后一次调用就可以了。
测量结束后,RecyclerView的onLayout(boolean, int, int, int, int)被调用。RecyclerView检查在测量过程中是否进行了布局计算,如果是,则重用其相关的信息。如果最后一次的measure spec与最终的大小不匹配,或adapter的数据在measure和layout过程中被更改,它也可能再次调用onLayoutChildren.
最后的最后,计算动画然后执行。
上面就是对测量流程的粗略描述。下面看下代码。
onMeasure被调用时,mState.mLayoutStep默认为STEP_START,开始布局。调到dispatchLayoutStep1() 分发布局步骤1. 这个方法的注释表示:第一步执行以下操作:
1.处理adapter的更新
2.确定要执行的动画
3.保存当前views的信息
4.如果必要,运行可预测布局,并保存其信息
网友评论
采用的是:
public void refresh(List<OfficeMattersInfo.WfsBean> newItems){
int startPosition = getStart();
int preSize = this.list.size();
if(preSize > 0) {
this.list.clear();
notifyItemRangeRemoved(startPosition, preSize);
}
this.list.addAll(newItems);
notifyItemRangeChanged(startPosition, newItems.size());
}
但是,在notifyItemRangeChanged处抛:IllegalArgumentException:
Called attach on a child which is not detached: ViewHolder{d69c5ef position=2 id=-1, oldPos=-1, pLpos:-1}
at android.support.v7.widget.RecyclerView$5.attachViewToParent(RecyclerView.java:779)
不知道为什么,怎么解决。。