前提
我们以RecycleView 的宽高都是Match_Parent作为前提去分析,这样onMeasure方法就不会调用dispatchStep1、dispatchStep2.我们只看onLayout 方法
ViewHolder 一些属性
public abstract static class ViewHolder {
@NonNull
public final View itemView;
WeakReference<RecyclerView> mNestedRecyclerView;
int mPosition = NO_POSITION;
int mOldPosition = NO_POSITION;
long mItemId = NO_ID;
int mItemViewType = INVALID_TYPE;
//之前
int mPreLayoutPosition = NO_POSITION;
mPreLayoutPosition 用于dispatchLayoutStep1,表示当前ViewHolder的位置,mPosition 用于dispatchLayoutStep2 时表示ViewHolder的位置。
getLayoutPosition
public final int getLayoutPosition() {
return mPreLayoutPosition == NO_POSITION ? mPosition : mPreLayoutPosition;
}
getLayoutPosition 如果当前是PreLayout阶段(dispatchLayoutStep1方法),那么该方法返回的就是mPreLayoutPosition,如果不是PreLayout阶段(dispatchLayoutStep2方法),就会返回mPosition.
首次进去RecycleView 页面
androidx.recyclerview.widget.RecyclerView#onLayout
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
dispatchLayout();
mFirstLayoutComplete = true;
}
里面调用了dispatchLayout 方法
androidx.recyclerview.widget.RecyclerView#dispatchLayout
void dispatchLayout() {
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();
}
里面layout 会分成三步。
dispatchLayoutStep1
private void dispatchLayoutStep1() {
mState.assertLayoutStep(State.STEP_START);
mState.mIsMeasuring = false;
//用于控制在layout期间 requestLayout不会生效
startInterceptRequestLayout();
//首次进去页面不会有任何处理 当我们调用notifyItemRemove 会修改oldposition
processAdapterUpdatesAndSetAnimationFlags();
saveFocusInfo();
mState.mTrackOldChangeHolders = mState.mRunSimpleAnimations && mItemsChanged;
mItemsAddedOrRemoved = mItemsChanged = false;
mState.mInPreLayout = mState.mRunPredictiveAnimations;
mState.mItemCount = mAdapter.getItemCount();
if (mState.mRunPredictiveAnimations) {
clearOldPositions();
} else {
//清楚OldPosition PreLayoutPosition
clearOldPositions();
}
onExitLayoutOrScroll();
stopInterceptRequestLayout(false);
mState.mLayoutStep = State.STEP_LAYOUT;
}
第一步主要进行ViewHolder的一些数据更新操作,比如ViewHolder 某个元素被移除,那么后面的ViewHolder的Position 都会被减一,同时如果RecycleView 有动画,那么会进行PreLayout,拿到动画位置的信息。
dispatchLayoutStep2
private void dispatchLayoutStep2() {
// Step 2: Run layout
mState.mInPreLayout = false;
mLayout.onLayoutChildren(mRecycler, mState);
mState.mStructureChanged = false;
mPendingSavedState = null;
}
真正的进行布局,如果在dispatchLayoutStep1进行过PreLayout,那么本次的布局所有的ViewHolder都是存在的,因为之前PreLayout 都已经进行了创建。
dispatchLayoutStep3
private void dispatchLayoutStep3() {
mState.assertLayoutStep(State.STEP_ANIMATIONS);
onEnterLayoutOrScroll();
mState.mLayoutStep = State.STEP_START;
if (mState.mRunSimpleAnimations) {
//执行动画
}
mLayout.removeAndRecycleScrapInt(mRecycler);
mState.mPreviousLayoutItemCount = mState.mItemCount;
}
主要进行动画的处理,回收Scrap的ViewHolder到RecycleViewPool中,比如被移除掉的ViewHolder.
调用adapter.notifyItemRemoved()方法 onLayout执行分析
注意,本篇我们以有动画的RecyclerView 进行分析,如果RecyclerView 没有动画,那么不会执行PreLayout,也就是在dispatchLayoutStep1里面并不会进行布局的填充。
第一步,更新Position
at androidx.recyclerview.widget.RecyclerView$ViewHolder.offsetPosition(RecyclerView.java:11106)
at androidx.recyclerview.widget.RecyclerView$ViewHolder.flagRemovedAndOffsetPosition(RecyclerView.java:11095)
at androidx.recyclerview.widget.RecyclerView.offsetPositionRecordsForRemove(RecyclerView.java:4635)
at androidx.recyclerview.widget.RecyclerView$6.offsetPositionsForRemovingLaidOutOrNewView(RecyclerView.java:996)
at androidx.recyclerview.widget.AdapterHelper.postponeAndUpdateViewHolders(AdapterHelper.java:448)
at androidx.recyclerview.widget.AdapterHelper.applyRemove(AdapterHelper.java:183)
at androidx.recyclerview.widget.AdapterHelper.preProcess(AdapterHelper.java:102)
at androidx.recyclerview.widget.RecyclerView.processAdapterUpdatesAndSetAnimationFlags(RecyclerView.java:3793)
at androidx.recyclerview.widget.RecyclerView.dispatchLayoutStep1(RecyclerView.java:4039)
at androidx.recyclerview.widget.RecyclerView.dispatchLayout(RecyclerView.java:3849)
at androidx.recyclerview.widget.RecyclerView.onLayout(RecyclerView.java:4404)
在dispatchLayoutStep1 方法里面,会进行更新oldposition、preLayoutPostion,preLayoutPosition 有了之后,就可以执行dispatchLayoutStep1 里面的布局.
offsetPosition() 更新oldposition、preLayoutPostion
androidx.recyclerview.widget.RecyclerView.ViewHolder#offsetPosition
void offsetPosition(int offset, boolean applyToPreLayout) {
if (mOldPosition == NO_POSITION) {
mOldPosition = mPosition;
}
if (mPreLayoutPosition == NO_POSITION) {
mPreLayoutPosition = mPosition;
}
if (applyToPreLayout) {
mPreLayoutPosition += offset;
}
mPosition += offset;
if (itemView.getLayoutParams() != null) {
((LayoutParams) itemView.getLayoutParams()).mInsetsDirty = true;
}
}
比如当一个item 移除掉,如果RecycleView 里面还有多余的元素,prelayout会加载没有下一个ViewHolder.
在布局的时候,移除的view 不会被计算占用的空间
androidx.recyclerview.widget.LinearLayoutManager#layoutChunk
// Consume the available space if the view is not removed OR changed
if (params.isItemRemoved() || params.isItemChanged()) {
result.mIgnoreConsumed = true;
}
androidx.recyclerview.widget.LinearLayoutManager#fill
if (!layoutChunkResult.mIgnoreConsumed || layoutState.mScrapList != null
|| !state.isPreLayout()) {
layoutState.mAvailable -= layoutChunkResult.mConsumed;
// we keep a separate remaining space because mAvailable is important for recycling
remainingSpace -= layoutChunkResult.mConsumed;
}
当dispatchLayoutStep1 执行完毕,清除preLayoutPosition,进行dispatchLayoutStep2
androidx.recyclerview.widget.RecyclerView#dispatchLayoutStep1
androidx.recyclerview.widget.RecyclerView.ViewHolder#clearOldPosition
// we don't process disappearing list because they may re-appear in post layout pass.
clearOldPositions();
dispatchLayoutStep3 回收被移除的ViewHolder
androidx.recyclerview.widget.RecyclerView.Recycler#recycleViewHolderInternal
at androidx.recyclerview.widget.RecyclerView$Recycler.recycleViewHolderInternal(RecyclerView.java:6429)
at androidx.recyclerview.widget.RecyclerView$Recycler.quickRecycleScrapView(RecyclerView.java:6554)
at androidx.recyclerview.widget.RecyclerView$LayoutManager.removeAndRecycleScrapInt(RecyclerView.java:9249)
at androidx.recyclerview.widget.RecyclerView.dispatchLayoutStep3(RecyclerView.java:4207)
总结:
首页进去RecycleView,只会在dispatchLayoutStep2 方法里面,对RecycleView 的布局进行填充。
如果我们调用了notifyItemRemoved 方法,在dispatchLayoutStep1 方法里面,会先更新OldPosition、PreLayoutPosition. 然后执行layout,layout之前会先调用mLayout.onLayoutChildren(mRecycler, mState);
在这个方法里面,会根据PreLayoutPosition 进行布局,因为有item 被移除,那么肯定会多出来空间,这时候,就会调用androidx.recyclerview.widget.LinearLayoutManager#fill
方法就会填充View,就会创建新的ViewHolder,在dispatchLayoutStep1 的最后,会把PreLayoutPosition 置为-1,这样就不影响dispatchLayoutStep2的布局。dispatchLayoutStep2 布局的时候,就会使用diapatchLayoutStep1创建的ViewHolder. 在dispatchStep3中,进行动画的处理,动画结束后,会把移除的ViewHolder 放到RecycleViewPool中重复利用。