持续学习,告别职业焦虑~
每次一个告别emo
小技巧,今天带来RecyclerView#LayoutManager
。😏~其实是间接的进入布局三件套主题。
相信大家对于LayoutManager
的使用肯定都非常熟悉了,如果还不熟悉或者不知道这个是干啥的,本篇文章可能也就不适合你了。
由于RecyclerView
内部包含内容确实比较多,鉴于本人可能理解方面会存在一些偏差,欢迎同样学习RecyclerView
源码的同学指正交流。手动狗头~
接下来就直接从源代码实现看一下LayoutManager
能带给我们什么惊喜吧~
RecycerView#setLayoutManager(@Nullable LayoutManager layout)
这个方法本身是平常使用LayoutManager
的一个入口。
public void setLayoutManager(@Nullable LayoutManager layout) {
if (layout == mLayout) {
return;
}
stopScroll();
if (mLayout != null) {
if (mItemAnimator != null) {
mItemAnimator.endAnimations();
}
mLayout.removeAndRecycleAllViews(mRecycler);
mLayout.removeAndRecycleScrapInt(mRecycler);
mRecycler.clear();
if (mIsAttached) {
mLayout.dispatchDetachedFromWindow(this, mRecycler);
}
mLayout.setRecyclerView(null);
mLayout = null;
} else {
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.exceptionLabel());
}
mLayout.setRecyclerView(this);
if (mIsAttached) {
mLayout.dispatchAttachedToWindow(this);
}
}
mRecycler.updateViewCacheSize();
requestLayout();
}
上面的代码逻辑上主要处理了两件下:
- 1、如果
LayoutManager
重新设置新的,则将之前的进行解绑操作,并将新的进行重新绑定。 - 2、使用
requestLayout
通知布局重新执行onMeasure
和onLayout
。
如果不清楚为什么执行
requestLayout
会导致父容器调用执行onMeasure
和onLayout
的同学,可以看下这篇文章,Android View 深度分析requestLayout、invalidate与postInvalidate
顺着这个思路接下来就看一下onMeasure
和onLayout
方法。
RecyclerView#onMeasure
这个方法牵扯到很多很有用的东西,虽然牵扯方法很多,也很长,但是还是建议整体看完。
里面可能涉及到一些比较核心的方法,为了整体理解流程不中断,会在下面单独列出来代码说明。
protected void onMeasure(int widthSpec, int heightSpec) {
/**
* 如果没有设置layout, 则使用defaultOnMeasure进行测量defaultOnMeasure方法则是根据
* widthSpec和heightSpac计算出来测试模式和测试大小,在集合padding值进行一个测量。
* 具体规则是:如果是EXACTLY,则直接使用测量大小,AT_MOST的话,则在
* ((paddingLeft+paddingRight)或者(paddingTop + paddingBottom)),与
* (ViewCompat.getMinimumWidth(this)或者ViewCompat.getMinimumHeight(this))最大值,
* 再与计算值之间取最小值。如果是UNSPECIFIED的话,则是使用((paddingLeft+paddingRight)
* 或者(paddingTop + paddingBottom)),与(ViewCompat.getMinimumWidth(this)或者
* ViewCompat.getMinimumHeight(this))最大值
*/
if (mLayout == null) {
defaultOnMeasure(widthSpec, heightSpec);
return;
}
/**
* RecyclerView#isAutoMeasureEnabled,这个方法决定了onMeasure计算是由LayoutManager负责,
* 还是由RecyclerView自己负责。isAutoMeasureEnabled的默认值为false。
* 而LayoutManager#isAutoMeasureEnabled作用是相似的,
* 系统为我们提供的LinearLayoutManager就是直接返回true,由Layout自身负责onMeasure测量的。
*/
if (mLayout.isAutoMeasureEnabled()) {
final int widthMode = MeasureSpec.getMode(widthSpec);
final int heightMode = MeasureSpec.getMode(heightSpec);
/**
* This specific call should be considered deprecated and replaced with
* {@link #defaultOnMeasure(int, int)}. It can't actually be replaced as it could
* break existing third party code but all documentation directs developers to not
* override {@link LayoutManager#onMeasure(int, int)} when
* {@link LayoutManager#isAutoMeasureEnabled()} returns true.
*/
//=====================这是个分割线======================
/**
* 从LayoutManabger#onMeasure代码发现实现其实就是上面的
* recyclerView#defaultOnMeasure方法,
* 虽然理论上是可以使用recyclerView#defaultOnMeasure直接替代
* LayoutManager#onMeasure,但是由于一些老旧代码的实现、引用限制,
* 目前还不能完全被替代。当然如果是自己实现LayoutManager的话,
* 官方并不建议复写LayoutManager#onMeasure方法。
*/
mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);
/**
* 如果当前测量模式是exactly,并且没有设置adapter,则跳过下面的测量流程
* 上面代码只测量了recyclerView的大小,并没有测量内部组件的大小
*/
final boolean measureSpecModeIsExactly =
widthMode == MeasureSpec.EXACTLY && heightMode == MeasureSpec.EXACTLY;
if (measureSpecModeIsExactly || mAdapter == null) {
return;
}
//mLayoutStep初始状态就是STEP_START
if (mState.mLayoutStep == State.STEP_START) {
/**
* 首次布局,主要涉及以下操作:
* 1、执行adapter的更新
* 2、决定哪一个动画执行
* 3、保存当前view的信息
* 4、如果需要,执预布局操作,并保存相关信息
*
* ===============分割线========================
* 这里面进行了一些初始化操作,会做判断,是否处理动画执行准备工作
*/
dispatchLayoutStep1();
}
// set dimensions in 2nd step.
// Pre-layout should happen with old dimensions for consistency
//将之前计算的容器的高度、宽度和测量模式设置给LayoutManager
mLayout.setMeasureSpecs(widthSpec, heightSpec);
mState.mIsMeasuring = true;
/**
* 这个方法可能在被多次调用,该方法用来对子itemview进行测量和排列
* 这里测量布局核心使用到mLayout.onLayoutChildren(mRecycler, mState);
* 该方法需要LayoutManager自行实现。
*
* ===================分割线=========================
* 万变不离其宗,涉及到计算排列,肯定离不开view.measure和view.layout方法。
* 跟踪下来发现主要涉及到方法:
* measureChildWithMargins(view, 0, 0)--负责测量 和
* layoutDecoratedWithMargins(view, left, top, right, bottom) -负责排列
*/
dispatchLayoutStep2();
// now we can get the width and height from the children.
//这里可以理解成根据计算出来的子布局的大小,重新决定出来recyclerview实际的大小
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.
/**
* 判断是否需要计算两次,默认false,实际根据LayoutManager的实现类具体处理,
* 在LinearLayoutManager中,发现判断是经过上面的处理之后,只有width和height中,
* 还有一个值的测量模式不是Exactly(也就不是确定值),则需要进行下面的二次测量
*/
if (mLayout.shouldMeasureTwice()) {
//常规操作了,首先强制使用Exactly模式,将已经测量的值,进行设置到MeasureSpec中
mLayout.setMeasureSpecs(
MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(getMeasuredHeight(), MeasureSpec.EXACTLY));
mState.mIsMeasuring = true;
//执行上面的itemView测量排列,上面有说过
dispatchLayoutStep2();
// now we can get the width and height from the children.
//通过计算出来的子布局大小,重新确定recyclerView实际大小
mLayout.setMeasuredDimensionFromChildren(widthSpec, heightSpec);
}
} else {
if (mHasFixedSize) {
mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);
return;
}
// custom onMeasure
if (mAdapterUpdateDuringMeasure) {
startInterceptRequestLayout();
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;
stopInterceptRequestLayout(false);
} else if (mState.mRunPredictiveAnimations) {
// If mAdapterUpdateDuringMeasure is false and mRunPredictiveAnimations is true:
// this means there is already an onMeasure() call performed to handle the pending
// adapter change, two onMeasure() calls can happen if RV is a child of LinearLayout
// with layout_width=MATCH_PARENT. RV cannot call LM.onMeasure() second time
// because getViewForPosition() will crash when LM uses a child to measure.
setMeasuredDimension(getMeasuredWidth(), getMeasuredHeight());
return;
}
if (mAdapter != null) {
mState.mItemCount = mAdapter.getItemCount();
} else {
mState.mItemCount = 0;
}
startInterceptRequestLayout();
mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);
stopInterceptRequestLayout(false);
mState.mInPreLayout = false; // clear
}
}
dispatchLayoutStep1
和dispatchLayoutStep2
以及dispatchLayoutStep3
在上面代码recyclerView#onMeasure
中有这么一段代码,而且如果你再去阅读dispatchLayout
方法的话,也会发现类似的操作。只是在dispatchLayout
中多了一个dispatchLayoutStep3
方法。
if (mState.mLayoutStep == State.STEP_START) {
dispatchLayoutStep1();
}
...
dispatchLayoutStep2();
在上面的onMeasure
中,我们解释了说dispatchLayoutStep1
当中处理了一些状态和信息初始化,以及普通加载动画和预加载视图动画的准备工作。而dispatchLayoutStep2
中处理了真实的子view的测量和排列工作。下面将具体实现详细说明下。
在进入正题之前,这里还有两个比较重要的类,需要说明下:
RecyclerView#State
这个类包含了当前recyclerView
的一些很有用的信息,比如目标滚动位置或者获取焦点的view数据,通常情况下,RecyclerView组件需要在彼此之间传递信息,为了在组件之间提供定义良好的数据总线,RecyclerView将传递相同的状态对象到组件的回调,这些组件可以使用它来交换数据。
STEP_START
(初始化状态),STEP_LAYOUT
(执行完dispatchLayoutStep1
之后,改变为该状态),STEP_ANIMATIONS
(执行完dispatchLayoutStep2
之后,改变为该状态)。ViewInfoStore
这个类是用来在执行动画的时候追踪view视图的一个辅助类,说白了就是协助view视图动态的。
在ViewInfoStore
中,会在不同状态对view
数据和animationInfo
进行分别保存。在dispatchLayoutStep1
阶段,使用mViewInfoStore.addToPreLayout(holder, animationInfo);
进行保存,可以发现这里保存的是preLayout
。而在dispatchLayoutStep3
中使用mViewInfoStore.addToPostLayout(holder, animationInfo);
进行保存。并且所有的操作完成之后,会使用过mViewInfoStore.process(mViewInfoProcessCallback);
进行执行动画。
dispatchLayoutStep1
private void dispatchLayoutStep1() {
mState.assertLayoutStep(State.STEP_START);
fillRemainingScrollValues(mState);
mState.mIsMeasuring = false;
startInterceptRequestLayout();
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) {
/**
* 找出所有没有删除的item,预加载item
* int getChildCount() {
* return mCallback.getChildCount() - mHiddenViews.size();
* }
*/
int count = mChildHelper.getChildCount();
for (int i = 0; i < count; ++i) {
//通过childView找到Viewholder
final ViewHolder holder = getChildViewHolderInt(mChildHelper.getChildAt(i));
if (holder.shouldIgnore() || (holder.isInvalid() && !mAdapter.hasStableIds())) {
continue;
}
/**
* recordPreLayoutInformation方法在layout之前,由recyclerView调用。
* Item animator在view可能执行rebound, moved or removed之前保存view的信息。
* ItemHolderInfo 信息将在layout布局完成之后回传给animate方法。
*/
final ItemHolderInfo animationInfo = mItemAnimator
.recordPreLayoutInformation(mState, holder,
ItemAnimator.buildAdapterChangeFlagsForAnimations(holder),
holder.getUnmodifiedPayloads());
//addToPreLayout 方法用来追踪paylayout的item的信息
mViewInfoStore.addToPreLayout(holder, animationInfo);
if (mState.mTrackOldChangeHolders && holder.isUpdated() && !holder.isRemoved()
&& !holder.shouldIgnore() && !holder.isInvalid()) {
long key = getChangedHolderKey(holder);
mViewInfoStore.addToOldChangeHolders(key, holder);
}
}
}
if (mState.mRunPredictiveAnimations) {
saveOldPositions();
final boolean didStructureChange = mState.mStructureChanged;
mState.mStructureChanged = false;
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);
}
}
}
clearOldPositions();
} else {
clearOldPositions();
}
onExitLayoutOrScroll();
stopInterceptRequestLayout(false);
mState.mLayoutStep = State.STEP_LAYOUT;
}
上面的代码,除了一些状态初始化和对状态、数据保存之外,很大代码篇幅是在两个if
判断之中。if (mState.mRunSimpleAnimations) {...}
和if (mState.mRunPredictiveAnimations) {...}
。但是通过断点和阅读代码发现,其实onMeasure
的时候这两个状态都是false,所以并不会进入该代码块执行。
而dispatchLayoutStep1
方法在dispatchLayout
方法中也会继续调用执行。对于mRunSimpleAnimations
和mRunPredictiveAnimations
的状态改变,其实也是在dispatchLayoutStep1
中执行状态判断之前的processAdapterUpdatesAndSetAnimationFlags
方法中执行的。
private void processAdapterUpdatesAndSetAnimationFlags() {
...
boolean animationTypeSupported = mItemsAddedOrRemoved || mItemsChanged;
mState.mRunSimpleAnimations = mFirstLayoutComplete
&& mItemAnimator != null
&& (mDataSetHasChangedAfterLayout
|| animationTypeSupported
|| mLayout.mRequestedSimpleAnimations)
&& (!mDataSetHasChangedAfterLayout
|| mAdapter.hasStableIds());
mState.mRunPredictiveAnimations = mState.mRunSimpleAnimations
&& animationTypeSupported
&& !mDataSetHasChangedAfterLayout
&& predictiveItemAnimationsEnabled();
}
mItemAnimator
默认是使用的SimpleItemAnimator
类,mFirstLayoutComplete
设置true
是在onLayout
的dispatchLayout
执行之后设置的。因此大胆猜测,dispatchLayoutStep1
中对于mRunSimpleAnimations
和mRunPredictiveAnimations
只在Item执行删除、新增、内容改变的时候才会执行if
里面的操作。
简单总结下dispatchLayout1
方法,可以理解这个方法主要是初始和保存一些状态和item信息,方便如果预加载布局在item删除、新增、变化的时候执行动画。
dispatchLayoutStep2
private void dispatchLayoutStep2() {
startInterceptRequestLayout();
...
mLayout.onLayoutChildren(mRecycler, mState);
...
//可以看到是设置mState的动态执行状态
mState.mRunSimpleAnimations = mState.mRunSimpleAnimations && mItemAnimator != null;
//设置layoutStep状态
mState.mLayoutStep = State.STEP_ANIMATIONS;
...
stopInterceptRequestLayout(false);
}
上面省略了其他代码,我们只关心核心代码实现。
public void onLayoutChildren(Recycler recycler, State state) {
Log.e(TAG, "You must override onLayoutChildren(Recycler recycler, State state) ");
}
咦~,有没有点惊喜,这个方法没有进行默认实现,需要LayoutManager
的具体实现做override
。那么就从LinearLayoutManager
入手看看吧。
LinearLayoutManager#onLayoutChildren
public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
...
//执行清理工作
detachAndScrapAttachedViews(recycler);
if (mAnchorInfo.mLayoutFromEnd) {
...
fill(recycler, mLayoutState, state, false);
...
} else {
...
fill(recycler, mLayoutState, state, false);
...
}
...
//为预加载动画重新设置布局-内部还是执行fill
layoutForPredictiveAnimations(recycler, state, startOffset, endOffset);
...
}
发现核心其实就是fill
方法,继续跟进
int fill(RecyclerView.Recycler recycler, LayoutState layoutState,
RecyclerView.State state, boolean stopOnFocusable) {
...
int remainingSpace = layoutState.mAvailable + layoutState.mExtraFillSpace;
LayoutChunkResult layoutChunkResult = mLayoutChunkResult;
while ((layoutState.mInfinite || remainingSpace > 0) && layoutState.hasMore(state)) {
...
layoutChunk(recycler, state, layoutState, layoutChunkResult);
...
}
...
return start - layoutState.mAvailable;
}
继续…layoutChunk
void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state,
LayoutState layoutState, LayoutChunkResult result) {
...
/**
* 这里引一个题外话,如果自定义LayoutManager的话,
* 必须要实现LayoutManager的generateDefaultLayoutParams方法。
*/
RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) view.getLayoutParams();
if (layoutState.mScrapList == null) {
if (mShouldReverseLayout == (layoutState.mLayoutDirection
== LayoutState.LAYOUT_START)) {
//1️⃣
addView(view);
} else {
addView(view, 0);
}
} else {
if (mShouldReverseLayout == (layoutState.mLayoutDirection
== LayoutState.LAYOUT_START)) {
//2️⃣
addDisappearingView(view);
} else {
addDisappearingView(view, 0);
}
}
//3️⃣
measureChildWithMargins(view, 0, 0);
result.mConsumed = mOrientationHelper.getDecoratedMeasurement(view);
int left, top, right, bottom;
if (mOrientation == VERTICAL) {
//略掉部分代码...主要用来做rtl、mLayoutDirection 等处理。
} else {
//略掉部分代码...主要用来做rtl、mLayoutDirection 等处理。
}
//4️⃣
layoutDecoratedWithMargins(view, left, top, right, bottom);
if (params.isItemRemoved() || params.isItemChanged()) {
result.mIgnoreConsumed = true;
}
result.mFocusable = view.hasFocusable();
}
上面代码中,我在四个比较重要的方法上面标记了数字,下面分别说一下。
- 1️⃣ addView(view);
最终其实执行的是private void addViewInt(View child, int index, boolean disappearing)
,只是这里第三个参数disappearing
为false。这个参数确定是否在onLayoutChilden
调用的时候执行动画。只在之前是被gone
掉或者remove
掉的时候才有效果。最终动画处理类其实还是ItemAnimator
。 - 2️⃣ addDisappearingView(view);
和上面addView
执行类似,只是这里的disappearing
参数是true,即会执行动画。 - 3️⃣ measureChildWithMargins(view, 0, 0);
这个方法执行的是一个标准的view测量流程,与传统流程相较增加了一个ItemDecoration的offset。最终执行child.measure(widthSpec, heightSpec);
- 4️⃣ layoutDecoratedWithMargins(view, left, top, right, bottom);
这个方法执行也是标准的view布局流程。使用child.layout(left, top, right, bottom)
进行标准的布局排列。当然和measure
的时候一样,这里需要针对ItemDecoration
对上下左右进行offset的处理。
RecyclerView#onLayout
RecyclerView
区别于以往的自定义View流程,在onMeasure
中其实已经做了排列操作,所以RecyclerView#onLayout
的整体流程也比较简单了,而且大部分也是上面onMeasure
流程中说明过的方法。
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
TraceCompat.beginSection(TRACE_ON_LAYOUT_TAG);
//核心在这里
dispatchLayout();
TraceCompat.endSection();
//在dispatchLayoutStep1中,判断是否执行runAnimation操作需要用到判断
mFirstLayoutComplete = true;
}
dispatchLayout
关于dispatchLayout的注释会发现传达了很重要的信息,可以帮助理解很多内容。
/**
* Wrapper around layoutChildren() that handles animating changes caused by layout.
* Animations work on the assumption that there are five different kinds of items
* in play:
* PERSISTENT: items are visible before and after layout
* REMOVED: items were visible before layout and were removed by the app
* ADDED: items did not exist before layout and were added by the app
* DISAPPEARING: items exist in the data set before/after, but changed from
* visible to non-visible in the process of layout (they were moved off
* screen as a side-effect of other changes)
* APPEARING: items exist in the data set before/after, but changed from
* non-visible to visible in the process of layout (they were moved on
* screen as a side-effect of other changes)
* The overall approach figures out what items exist before/after layout and
* infers one of the five above states for each of the items. Then the animations
* are set up accordingly:
* PERSISTENT views are animated via
* {@link ItemAnimator#animatePersistence(ViewHolder, ItemHolderInfo, ItemHolderInfo)}
* DISAPPEARING views are animated via
* {@link ItemAnimator#animateDisappearance(ViewHolder, ItemHolderInfo, ItemHolderInfo)}
* APPEARING views are animated via
* {@link ItemAnimator#animateAppearance(ViewHolder, ItemHolderInfo, ItemHolderInfo)}
* and changed views are animated via
* {@link ItemAnimator#animateChange(ViewHolder, ViewHolder, ItemHolderInfo, ItemHolderInfo)}.
*/
从这个方法注释,可以发现,这个的核心其实是帮助我们处理动画相关的,核心点已经不是单纯的布局相关了,当然动画变化也会导致对应的测量、布局动画。但是从这块瞬间就能理解到dispatchLayoutStep1
中有关于为动画执行就行的一系列操作了。
void dispatchLayout() {
//没有设置adapter,直接跳过,回顾下 onmeasure中其实也只是使用defaultMeasure计算了下
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()) {
mLayout.setExactMeasureSpecsFrom(this);
dispatchLayoutStep2();
} else {
// always make sure we sync them (to ensure mode is exact)
mLayout.setExactMeasureSpecsFrom(this);
}
/**
* 对itemView的测量、排列的最终步骤,主要用来保存一些动画使用的数据,
* 并且触发动画并做必要的清理工作。
*/
dispatchLayoutStep3();
}
dispatchLayoutStep3
private void dispatchLayoutStep3() {
mState.assertLayoutStep(State.STEP_ANIMATIONS);
startInterceptRequestLayout();
onEnterLayoutOrScroll();
mState.mLayoutStep = State.STEP_START;
/**
* 在dispatchLayoutState1和dispatchLayoutState2中操作之后,
* mRunSimpleAnimations=true,所以会执行下面的动画
*/
if (mState.mRunSimpleAnimations) {
for (int i = mChildHelper.getChildCount() - 1; i >= 0; i--) {
ViewHolder holder = getChildViewHolderInt(mChildHelper.getChildAt(i));
...
ViewHolder oldChangeViewHolder = mViewInfoStore.getFromOldChangeHolders(key);
if (oldChangeViewHolder != null && !oldChangeViewHolder.shouldIgnore()) {
...
mViewInfoStore.addToPostLayout(holder, animationInfo);
...
} else {
mViewInfoStore.addToPostLayout(holder, animationInfo);
}
}
//Process view info lists and trigger animations
mViewInfoStore.process(mViewInfoProcessCallback);
}
...
recoverFocusFromState();
resetFocusInfo();
}
对于上面的代码,主要关注两个方法。mViewInfoStore.addToPostLayout(holder, animationInfo);
和mViewInfoStore.process(mViewInfoProcessCallback);
。
针对 mViewInfoStore.addToPostLayout(holder, animationInfo);
方法,聪明的你应该已经发现其实是和dispatchLayoutStep1
中的addToPreLayout
成对使用的。而且刚好流程是pre
是在真正的layoutChildren之前,而post
是在layout之后,因此可以大胆猜测,其实就是分别对布局变化(包括增加
、删除
、内容变化
)之前和之后的item状态进行了保存。之后使用mViewInfoStore.process
执行动画。
再看 mViewInfoStore.process(mViewInfoProcessCallback)
到底做了什么?
但从备注(Process view info lists and trigger animations
)可以发现,可以发现是触发动画执行。
void process(ProcessCallback callback) {
for (int index = mLayoutHolderMap.size() - 1; index >= 0; index--) {
final RecyclerView.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:/");
}
InfoRecord.recycle(record);
}
}
从上面代码,可以发现使用record.flag
判断了动画执行类型,在结合addToPostLayout
方法中设置record.flags |= FLAG_POST;
,刚好佐证了我们的猜测。
而callback
是一个接口,在recyclerView
中对它进行了实现。
private final ViewInfoStore.ProcessCallback mViewInfoProcessCallback =
new ViewInfoStore.ProcessCallback() {
@Override
public void processDisappeared(ViewHolder viewHolder, @NonNull ItemHolderInfo info,
@Nullable ItemHolderInfo postInfo) {
mRecycler.unscrapView(viewHolder);
animateDisappearance(viewHolder, info, postInfo);
}
@Override
public void processAppeared(ViewHolder viewHolder,
ItemHolderInfo preInfo, ItemHolderInfo info) {
animateAppearance(viewHolder, preInfo, info);
}
@Override
public void processPersistent(ViewHolder viewHolder,
@NonNull ItemHolderInfo preInfo, @NonNull ItemHolderInfo postInfo) {
viewHolder.setIsRecyclable(false);
if (mDataSetHasChangedAfterLayout) {
// since it was rebound, use change instead as we'll be mapping them from
// stable ids. If stable ids were false, we would not be running any
// animations
if (mItemAnimator.animateChange(viewHolder, viewHolder, preInfo,
postInfo)) {
postAnimationRunner();
}
} else if (mItemAnimator.animatePersistence(viewHolder, preInfo, postInfo)) {
postAnimationRunner();
}
}
@Override
public void unused(ViewHolder viewHolder) {
mLayout.removeAndRecycleView(viewHolder.itemView, mRecycler);
}
};
最终其实就是使用ItemAnimator
进行了动画处理。
void animateAppearance(@NonNull ViewHolder itemHolder,
@Nullable ItemHolderInfo preLayoutInfo, @NonNull ItemHolderInfo postLayoutInfo) {
itemHolder.setIsRecyclable(false);
if (mItemAnimator.animateAppearance(itemHolder, preLayoutInfo, postLayoutInfo)) {
postAnimationRunner();
}
}
当然SimpleItemAnimator
作为ItemAnimator
的默认实现,其内部实现也是我们熟悉的东西ValueAnimator
,并没有啥黑科技,感兴趣的可以自行前往看看。
至此对于RecyclerView
的测量(onMeasure
)、排列(onLayout
)流程分析完了。
谢谢阅读,期待您的指正交流。
谢谢阅读,期待您的指正交流。
谢谢阅读,期待您的指正交流。