简书无法编辑保存了?什么bug?还得回CSDN来写。
写在前面,看RecyclerView源码有点"老虎吃天,无从下口"的感觉。多次提笔想写关于RecyclerView的源码相关文章,最终也是作罢。最大的原因还是感觉RecyclerView的源码太过复杂,怕自己不能胜任。也是走马观花的看了一些网上的博客文章,有的文章看了也不止一遍。自己也就照虎画猫,来记录一下阅读源码的过程。
- 2024.03.15更新,但是也不是太难下口,哈哈。
分析场景:
- RecyclerView 使用 LinearLayoutManager ,从上到下布局,RecyclerView 的 宽高都是 MATCH_PARENT。
- 正常设置了LayoutManager和Adapter以后,RecyclerView的measure、layout、draw流程。
- 第一次是如何填充子View的。
- 在滚动过程中(move和fling)的时候,是如何回收和填充子View的。我们这里不会看回收和填充子View的细节,只会看哪里发生了回收和填充子View调用操作。
先说下分析的结论:
- RecyclerView 的 宽高都是 MATCH_PARENT。那么在 onMeasure的时候,是不会调用 dispatchLayoutStep1 和 dispatchLayoutStep2 的。
- 第一次设置LayoutManager和Adapter是没有动画效果的,可以认为 dispatchLayoutStep1 和 dispatchLayoutStep3 的 这两个动画动没起作用。
- dispatchLayoutStep2 方法,真正起布局作用的方法。内部调用 fill 方法,获取ViewHolder(新创建的,或者从缓存中获取的),测量、布局、添加到 RecyclerView。
- 第一次布局的时候,RecyclerView 的 childCount 还是0,是没有ViewHolder的回收和复用的。
5.就是在滑动和fling的时候,LayoutManager会调用fill
方法,会获取ViewHolder填充的RecyclerView,并把滑出RecyclerView的ViewHolder回收。
示例代码
LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this);
recyclerView.setLayoutManager(linearLayoutManager);
recyclerView.setAdapter(...);
使用RecyclerView的两部曲。setLayoutManager()
和setAdapter
。
RecyclerView的setLayoutManager方法。
public void setLayoutManager(@Nullable LayoutManager layout) {if (layout == mLayout) {return;}//停止滚动stopScroll();//改变LayoutManager依然会重用View。if (mLayout != null) {//...} else {//清除缓存mRecycler.clear();}// this is just a defensive measure for faulty item animators.mChildHelper.removeAllViewsUnfiltered();mLayout = layout;if (layout != null) {//...//LayoutManager关联RecyclerViewmLayout.setRecyclerView(this);if (mIsAttached) {mLayout.dispatchAttachedToWindow(this);}}mRecycler.updateViewCacheSize();//注释1处,调用requestLayout方法requestLayout();
}
注释1处,调用requestLayout方法。
RecyclerView的setAdapter方法。
public void setAdapter(@Nullable Adapter adapter) {// bail out if layout is frozensetLayoutFrozen(false);//注释1处setAdapterInternal(adapter, false, true);processDataSetCompletelyChanged(false);//注释2处,调用 requestLayout 方法requestLayout();
}
注释2处,调用 requestLayout 方法。
RecyclerView的setLayoutManager()
和setAdapter
方法内部都会调用requestLayout方法,请求measure、layout、draw。
RecyclerView的onMeasure方法。
@Override
protected void onMeasure(int widthSpec, int heightSpec) {if (mLayout == null) {//注释1处defaultOnMeasure(widthSpec, heightSpec);return;}//注释2处,是否开启自动测量,我们以LinearLayoutManager来分析,LinearLayoutManager默认是true。if (mLayout.isAutoMeasureEnabled()) {final int widthMode = MeasureSpec.getMode(widthSpec);final int heightMode = MeasureSpec.getMode(heightSpec);//LayoutManager的onMeasure方法默认还是调用了RecyclerView的defaultOnMeasure方法mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);//宽高的测量模式都是EXACTLY或者mAdapter为null直接return,就是使用defaultOnMeasure的测量结果final boolean measureSpecModeIsExactly =widthMode == MeasureSpec.EXACTLY && heightMode == MeasureSpec.EXACTLY;if (measureSpecModeIsExactly || mAdapter == null) {//注释2.1处,Note: 注意,当我们在布局里设置 RecyclerView 的宽高为 match_parent 的时候,这里的 widthMode 和 heightMode 都是 MeasureSpec.EXACTLY,会直接returnreturn;}//注释3处,当前布局阶段,默认是State.STEP_STARTif (mState.mLayoutStep == State.STEP_START) {//注释4处,dispatchLayoutStep1();}//为LayoutManager设置测量模式mLayout.setMeasureSpecs(widthSpec, heightSpec);//将RecyclerView的状态置为正在测量mState.mIsMeasuring = true;//注释5处,第二步布局dispatchLayoutStep2();//在dispatchLayoutStep2之后,重新设置LayoutManager的宽高信息mLayout.setMeasuredDimensionFromChildren(widthSpec, heightSpec);// 如果RecyclerView没有准确的宽高信息,或者RecyclerView至少有一个子View没有确定的宽高则重新测量。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 {//自动测量为false,这种场景我们就不看了,一刀切是狠,哈哈。//...}
}
注释2处,是否开启自动测量,我们以LinearLayoutManager来分析,LinearLayoutManager默认是true。
注释2.1处,Note: 注意,当我们在布局里设置 RecyclerView 的宽高为 match_parent 的时候, 这里的 widthMode 和 heightMode 都是 MeasureSpec.EXACTLY,会直接return。
注释3处以及后面的内容先不看,这里留一个问题,为什么会在 onMeasure 的时候,调用 dispatchLayoutStep1(); 和 dispatchLayoutStep2();
呢???后面再研究,这里先关注主流程。
接下来我们看看RecyclerView的onLayout方法。
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {TraceCompat.beginSection(TRACE_ON_LAYOUT_TAG);//注释1处,调用dispatchLayout方法。dispatchLayout();TraceCompat.endSection();//注释2处。标记第一次布局完成。mFirstLayoutComplete = true;
}
注释2处,标记第一次布局完成。
注释1处,调用dispatchLayout方法。
/*** 该方法可以看做是layoutChildren()方法的一个包装,处理由于布局造成的动画改变。* 动画的工作机制基于有5中不同类型的动画的假设:* PERSISTENT: 在布局前后,items一直可见。* REMOVED: 在布局之前items可见,在布局之后,items被移除。* ADDED: 在布局之前items不存在,items是被添加到RecyclerView的。* DISAPPEARING: 在布局前后items存在于数据集中,但是在布局过程中可见性由可见变为不可见。(这些items是由于其他变化的副作用而被移动到屏幕之外了)* APPEARING: 在布局前后items存在于数据集中,但是在布局过程中可见性由不可见变为可见。(这些items是由于其他变化的副作用而被移动到屏幕之中了)** 方法的大体逻辑就是计算每个item在布局前后是否存在,并推断出它们处于上述五种状态的哪一种,然后设置不同的动画。* PERSISTENT类型的Views 通过 ItemAnimator 的 animatePersistence(ViewHolder, ItemHolderInfo, ItemHolderInfo) 方法执行动画* DISAPPEARING类型的Views 通过 ItemAnimator 的 animateDisappearance(ViewHolder, ItemHolderInfo, ItemHolderInfo) 方法执行动画* APPEARING类型的Views 通过 ItemAnimator 的 animateAppearance(ViewHolder, ItemHolderInfo, ItemHolderInfo) 方法执行动画* REMOVED和ADDED类型 (notifyItemChange 的时候,其实是把老的 itemView 移除了,然后新添加了一个itemView。这个过程就是REMOVED和ADDED类型 )的Views 通过 ItemAnimator 的 animateChange(ViewHolder, ViewHolder, ItemHolderInfo, ItemHolderInfo) 执行动画。*/
void dispatchLayout() {//...mState.mIsMeasuring = false;if (mState.mLayoutStep == State.STEP_START) {//注释1处,调用dispatchLayoutStep1方法。dispatchLayoutStep1();mLayout.setExactMeasureSpecsFrom(this);//注释2处,调用dispatchLayoutStep2方法。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 {mLayout.setExactMeasureSpecsFrom(this);}//调用dispatchLayoutStep3方法。dispatchLayoutStep3();
}
注释1处,调用dispatchLayoutStep1方法。
/*** layout的第一步,在这个步骤会执行以下操作;* - 处理适配器更新* - 决定要运行哪种动画* - 保存当前views的信息* - 如果必要的话,运行预布局(layout)并保存相应的信息*/
private void dispatchLayoutStep1() {mState.assertLayoutStep(State.STEP_START);fillRemainingScrollValues(mState);mState.mIsMeasuring = false;//开始拦截requestLayout的请求,避免多次响应requestLayout的调用,造成多次布局startInterceptRequestLayout();//mViewInfoStore中存储的是要执行动画的Views的相关信息,这里清除mViewInfoStore.clear();onEnterLayoutOrScroll();//注释1处,处理适配器更新和设置动画的标志位。RecyclerView第一次布局,是没有什么动画效果的。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();//停止拦截requestLayout的请求,stopInterceptRequestLayout(false);//将当前布局步骤赋值为State.STEP_LAYOUTmState.mLayoutStep = State.STEP_LAYOUT;
}
第一次调用dispatchLayoutStep1的时候,此时RecyclerView还没有子View所以不会有什么动画执行。方法最后将mState.mLayoutStep
置为了State.STEP_LAYOUT
。
dispatchLayout方法注释2处,调用dispatchLayoutStep2方法。
/*** layout的第二步,在这里我们为最终状态执行View的真正的布局操作。如果必要的话,这个步骤可能会执行多次。*/
private void dispatchLayoutStep2() {startInterceptRequestLayout();onEnterLayoutOrScroll();mState.assertLayoutStep(State.STEP_LAYOUT | State.STEP_ANIMATIONS);//消耗所有的延迟更新,这里以后再看mAdapterHelper.consumeUpdatesInOnePass();//获取适配器中数据的数量mState.mItemCount = mAdapter.getItemCount();mState.mDeletedInvisibleItemCountSincePreviousLayout = 0;// 注释1处,将预布局状态置为false。// mInPreLayout 为 false 的时候,不会从 mChangedScrap 中获取 ViewHoldermState.mInPreLayout = false;//注释2处,布局子ViewmLayout.onLayoutChildren(mRecycler, mState);mState.mStructureChanged = false;mPendingSavedState = null;//为falsemState.mRunSimpleAnimations = mState.mRunSimpleAnimations && mItemAnimator != null;//这里将状态置为了State.STEP_ANIMATIONSmState.mLayoutStep = State.STEP_ANIMATIONS;onExitLayoutOrScroll();stopInterceptRequestLayout(false);
}
注释1处,将预布局状态置为false。mInPreLayout 为 false 的时候,不会从 Recycler.mChangedScrap 中查找缓存的 ViewHolder。
注释2处,调用LayoutManager的onLayoutChildren方法。我们直接看LinearLayoutManager的onLayoutChildren方法。
@Override
public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {// layout的算法:// 1) 通过检查children和其他变量,找到一个锚点坐标和锚点的位置(我认为应该是在adapter中数据对应的位置)。 // 2) 从锚点向上填充RecyclerView。// 3) 从锚点向下填充RecyclerView。// 4) 滚动RecyclerView,做一些显示上的调整。if (mPendingSavedState != null || mPendingScrollPosition != RecyclerView.NO_POSITION) {if (state.getItemCount() == 0) {removeAndRecycleAllViews(recycler);return;}}if (mPendingSavedState != null && mPendingSavedState.hasValidAnchor()) {mPendingScrollPosition = mPendingSavedState.mAnchorPosition;}//注释1处,如果mLayoutState为null的话,则创建。ensureLayoutState();mLayoutState.mRecycle = false;//决定布局顺序,是否要倒着布局。LinearLayoutManager默认是从上到下布局。resolveShouldLayoutReverse();//...//这里做了精简,extraForStart和extraForEnd我们都认为是0int extraForStart = 0;int extraForEnd = 0;//...int startOffset;int endOffset;final int firstLayoutDirection;if (mAnchorInfo.mLayoutFromEnd) {firstLayoutDirection = mShouldReverseLayout ? LayoutState.ITEM_DIRECTION_TAIL: LayoutState.ITEM_DIRECTION_HEAD;} else {//正常情况下,firstLayoutDirection为LayoutState.ITEM_DIRECTION_TAILfirstLayoutDirection = mShouldReverseLayout ? LayoutState.ITEM_DIRECTION_HEAD: LayoutState.ITEM_DIRECTION_TAIL;}onAnchorReady(recycler, state, mAnchorInfo, firstLayoutDirection);//注释3处,回收ViewHolder,第一次进来,RecyclerView是没有子View的,没有回收动作。后面再看。 detachAndScrapAttachedViews(recycler);mLayoutState.mInfinite = resolveIsInfinite();mLayoutState.mIsPreLayout = state.isPreLayout();// noRecycleSpace not needed: recycling doesn't happen in below's fill// invocations because mScrollingOffset is set to SCROLLING_OFFSET_NaNmLayoutState.mNoRecycleSpace = 0;if (mAnchorInfo.mLayoutFromEnd) {//正常情况为该条件不满足。我们分析else的情况。//...} else {//注释4处,向end方向填充的时候,先计算一些信息。updateLayoutStateToFillEnd(mAnchorInfo);mLayoutState.mExtraFillSpace = extraForEnd;//注释5处,从锚点开始向end方向填充fill(recycler, mLayoutState, state, false);//...}//...//如果必要的话,为预执行动画布局子View。第一次布局的时候,不会有预执行动画。layoutForPredictiveAnimations(recycler, state, startOffset, endOffset);//...}
注释1处,如果 mLayoutState 为null 的话,则创建。布局过程中用来保存布局状态,在布局结束的时候,状态就被重置了。
注释2处,计算锚点位置和坐标。可以简单认为锚点就是RecyclerView的paddingTop,默认为0。
注释3处,回收ViewHolder,第一次进来,RecyclerView是没有子View的,没有回收动作。后面再看。
注释4处,向end方向填充的时候,先计算一些信息。
private void updateLayoutStateToFillEnd(AnchorInfo anchorInfo) {//默认情况下,anchorInfo.mPosition 是0,anchorInfo.mCoordinate 是0,//在我们这个默认的例子中,anchorInfo.mCoordinate 其实就是 RecyclerView 的 paddingTop,默认是0updateLayoutStateToFillEnd(anchorInfo.mPosition, anchorInfo.mCoordinate);
}private void updateLayoutStateToFillEnd(int itemPosition, int offset) {//可填充的像素数,默认就是RecyclerView的高度减去paddingBottom,也就是RecyclerView的可用空间mLayoutState.mAvailable = mOrientationHelper.getEndAfterPadding() - offset;//默认是 LayoutState.ITEM_DIRECTION_TAIL;mLayoutState.mItemDirection = mShouldReverseLayout ? LayoutState.ITEM_DIRECTION_HEAD :LayoutState.ITEM_DIRECTION_TAIL;//从前面的分析我们知道,默认itemPosition是0mLayoutState.mCurrentPosition = itemPosition;mLayoutState.mLayoutDirection = LayoutState.LAYOUT_END;mLayoutState.mOffset = offset;//注释1处,这里注意一下,mLayoutState将mScrollingOffset置为LayoutState.SCROLLING_OFFSET_NaNmLayoutState.mScrollingOffset = LayoutState.SCROLLING_OFFSET_NaN;
}
注释1处,这里注意一下,mLayoutState将mScrollingOffset置为LayoutState.SCROLLING_OFFSET_NaN,后面会用到。
LayoutManager的onLayoutChildren方法的注释5处,调用 fill
方法从锚点开始向end方向(对于默认的LinearLayoutManager,就是从锚点向下)填充RecyclerView,传入的最后一个参数为false注意一下。
fill(recycler, mLayoutState, state, false);
LinearLayoutManager的fill方法。
/*** 神奇的方法:Fills the given layout, defined by the layoutState. * This is fairly independent from the rest of the {@link LinearLayoutManager} * and with little change, can be made publicly available as a helper class.* @param recycler 当前关联到RecyclerView的recycler。* @param layoutState 关于我们应该如何填写可用空间的配置。* @param state Context passed by the RecyclerView to control scroll steps.* @param stopOnFocusable 如果为true的话,遇到第一个可获取焦点的View则停止填充。* @return 返回添加的像素,对滚动方法有用。*/
int fill(RecyclerView.Recycler recycler, LayoutState layoutState,RecyclerView.State state, boolean stopOnFocusable) {// max offset we should set is mFastScroll + available//记录开始填充的时候,可用的空间final int start = layoutState.mAvailable;//...// 注释0处,记录剩余的可填充的空间int remainingSpace = layoutState.mAvailable + layoutState.mExtraFillSpace;LayoutChunkResult layoutChunkResult = mLayoutChunkResult;//注释1处,只要还有剩余空间remainingSpace并且还有数据,调用layoutChunk方法,获取ViewHolder 填充RecyclerView。while ((layoutState.mInfinite || remainingSpace > 0) && layoutState.hasMore(state)) {layoutChunkResult.resetInternal();//注释2处,获取并添加子View,然后测量、布局子View并将分割线考虑在内。layoutChunk(recycler, state, layoutState, layoutChunkResult);//如果没有更多View了,布局结束,跳出循环if (layoutChunkResult.mFinished) {break;}//注释3处,增加偏移量,加上已经填充的像素layoutState.mOffset += layoutChunkResult.mConsumed * layoutState.mLayoutDirection;if (!layoutChunkResult.mIgnoreConsumed || layoutState.mScrapList != null|| !state.isPreLayout()) {//注释4处,可用空间减去已经填充的像素 layoutState.mAvailable -= layoutChunkResult.mConsumed;// we keep a separate remaining space because mAvailable is important for recycling//注释5处,剩余空间,减去已经填充的像素remainingSpace -= layoutChunkResult.mConsumed;}if (stopOnFocusable && layoutChunkResult.mFocusable) {break;}}// 返回已经填充的空间,比如开始可用空间 start 是1920,填充完毕,可用空间 layoutState.mAvailable 是120,就返回 1800 。填充了1800像素。//返回结果有可能大于start,因为最后一个填充的View有一部分在屏幕外面。return start - layoutState.mAvailable;
}
注释0处,记录剩余的可填充的空间。
注释1处,只要还有剩余空间remainingSpace并且还有数据,调用layoutChunk方法,获取ViewHolder 填充RecyclerView。
注释2处,获取并添加子View,然后测量、布局子View并将分割线考虑在内。LinearLayoutManager的layoutChunk方法。
void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state,LayoutState layoutState, LayoutChunkResult result) {//注释1处,获取子View,可能是从缓存中或者新创建的View。后面分析缓存相关的点的时候再看。//取数据的顺序 mScrapList -> mRecycler(Recycler#mAttachedScrap或者Recycler#mChangedScrap ->ChildHelper#mHiddenViews -> Recycler#mCachedViews Recycler#mViewCacheExtension -> Recycler#mRecyclerPool ) -> (createViewHolder)View view = layoutState.next(recycler);if (view == null) {//注释2处,如果获取到的子View为null,将LayoutChunkResult的mFinished置为true,没有更多数据了,用于跳出循环然后直接return。result.mFinished = true;return;}RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) view.getLayoutParams();if (layoutState.mScrapList == null) {//mShouldReverseLayout == (layoutState.mLayoutDirection== LayoutState.LAYOUT_START) ,都等于false,判断两个 false 是否相等,返回trueif (mShouldReverseLayout == (layoutState.mLayoutDirection== LayoutState.LAYOUT_START)) {//注释3处,默认是从上到下布局的时候,添加子ViewaddView(view); } else {//注释4处addView(view, 0);}} //...//注释5处,测量子View的大小,包括margin和分割线。measureChildWithMargins(view, 0, 0);//注释6处,记录该View消耗的高度result.mConsumed = mOrientationHelper.getDecoratedMeasurement(view);int left, top, right, bottom;if (mOrientation == VERTICAL) {if (isLayoutRTL()) {right = getWidth() - getPaddingRight();left = right - mOrientationHelper.getDecoratedMeasurementInOther(view);} else {//注释6.1处,获取子View的左右坐标left = getPaddingLeft();right = left + mOrientationHelper.getDecoratedMeasurementInOther(view);}if (layoutState.mLayoutDirection == LayoutState.LAYOUT_START) {bottom = layoutState.mOffset;top = layoutState.mOffset - result.mConsumed;} else {//注释6.2处,获取子View的上下坐标top = layoutState.mOffset;bottom = layoutState.mOffset + result.mConsumed;}} //...//注释7处,布局子View,并将margin和分割线也考虑在内。layoutDecoratedWithMargins(view, left, top, right, bottom);// Consume the available space if the view is not removed OR changedif (params.isItemRemoved() || params.isItemChanged()) {result.mIgnoreConsumed = true;}result.mFocusable = view.hasFocusable();
}
注释1处,获取子View,可能是从缓存中或者新创建的View。后面分析缓存相关的点的时候再看。
注释2处,如果获取到的子View为null,将LayoutChunkResult的mFinished置为true,用于跳出循环然后直接return。
注释3处,默认是从上到下布局的时候,添加子View。
注释5处,测量子View的大小,包括margin和分割线。
注释6处,记录该View消耗的高度。
注释7处,布局子View,并将margin和分割线也考虑在内。
我们回到 fill 方法的注释3处,增加偏移量,加上已经填充的像素。
注释4处,可用空间减去已经填充的像素。
注释5处,剩余空间,减去已经填充的像素。如果没有剩余空间了或者没有更多View了,fill方法结束。
我们回到LayoutManager的onLayoutChildren方法的注释6处, 向start方向填充的时候,计算一些信息,逻辑和updateLayoutStateToFillEnd类似,不再赘述。
LayoutManager的onLayoutChildren方法的注释7处,调用fill方法继续填充。注意:第一次布局的时候,正常来说我们是从0开始向下填充的,所以就不会有向上填充的情况。我们先忽略。
到这里,dispatchLayoutStep2算是分析完了。接下来是 dispatchLayoutStep3方法。
RecyclerView的dispatchLayoutStep3方法。
/*** 布局的最后一步,在这里我们会保存和动画相关的Views的信息,触发动画并执行必要的清理工作。*/
private void dispatchLayoutStep3() {mState.assertLayoutStep(State.STEP_ANIMATIONS);startInterceptRequestLayout();onEnterLayoutOrScroll();//将状态重置为State.STEP_STARTmState.mLayoutStep = State.STEP_START;//是否执行动画if (mState.mRunSimpleAnimations) {//第一次布局,这个条件不满足//...}//...//标记布局完成。mLayout.onLayoutCompleted(mState);onExitLayoutOrScroll();stopInterceptRequestLayout(false);//清除mViewInfoStoremViewInfoStore.clear();if (didChildRangeChange(mMinMaxLayoutPositions[0], mMinMaxLayoutPositions[1])) {dispatchOnScrolled(0, 0);}recoverFocusFromState();resetFocusInfo();
}
对于第一次布局来说,可以认为 dispatchLayoutStep3 只是标记布局完成,清除 mViewInfoStore(里面保存了动画信息),动画相关信息我们先不看,到这里layout过程结束,下面继续看绘制过程。
RecyclerView#draw(Canvas c)
@Override
public void draw(Canvas c) {super.draw(c);//注释1处,在绘制结束后,调用 onDrawOver 绘制分割线final int count = mItemDecorations.size();for (int i = 0; i < count; i++) {//注释2处mItemDecorations.get(i).onDrawOver(c, this, mState);}
}@Override
public void onDraw(Canvas c) {super.onDraw(c);//先绘制分割线final int count = mItemDecorations.size();for (int i = 0; i < count; i++) {mItemDecorations.get(i).onDraw(c, this, mState);}
}
注释1处,RecyclerView调用了父类的draw方法。其中会先调用onDraw方法,RecyclerView重写了onDraw方法绘制了分割线。然后就是调用dispatchDraw方法在drawChild方法中绘制子View。然后在注释2处,再次绘制分割线。这也是为什么说我们自定义分割线的时候,只要重写ItemDecoration的onDraw或者onDrawOver一个方法就够了。两个都重写的话会导致绘制两次分割线。
第一次 measure,layout,draw 过程结束。平平无奇。
接下来我们看一看在滑动和fling的时候,RecyclerView的一些逻辑。
一句话概括这个过程:就是在滑动和fling的时候,会获取ViewHolder填充的RecyclerView,并把滑出RecyclerView的ViewHolder被回收。
RecyclerView的onTouchEvent方法。
@Override
public boolean onTouchEvent(MotionEvent e) {//...switch (action) {case MotionEvent.ACTION_DOWN: {//...} break;//...case MotionEvent.ACTION_MOVE: {final int index = e.findPointerIndex(mScrollPointerId);final int x = (int) (e.getX(index) + 0.5f);final int y = (int) (e.getY(index) + 0.5f);int dx = mLastTouchX - x;int dy = mLastTouchY - y;if (mScrollState != SCROLL_STATE_DRAGGING) {boolean startScroll = false;if (canScrollHorizontally) {//横向滑动的忽略...}if (canScrollVertically) {if (dy > 0) {dy = Math.max(0, dy - mTouchSlop);} else {dy = Math.min(0, dy + mTouchSlop);}if (dy != 0) {startScroll = true;}}if (startScroll) {//到达了滑动的条件,将滑动状态置为SCROLL_STATE_DRAGGINGsetScrollState(SCROLL_STATE_DRAGGING);}}if (mScrollState == SCROLL_STATE_DRAGGING) {//...//注释1处,if (scrollByInternal(canScrollHorizontally ? dx : 0,canScrollVertically ? dy : 0,e)) {getParent().requestDisallowInterceptTouchEvent(true);}}} break;case MotionEvent.ACTION_UP: {mVelocityTracker.addMovement(vtev);eventAddedToVelocityTracker = true;mVelocityTracker.computeCurrentVelocity(1000, mMaxFlingVelocity);final float xvel = canScrollHorizontally? -mVelocityTracker.getXVelocity(mScrollPointerId) : 0;final float yvel = canScrollVertically? -mVelocityTracker.getYVelocity(mScrollPointerId) : 0;//注释3处,如果速度够了,flingif (!((xvel != 0 || yvel != 0) && fling((int) xvel, (int) yvel))) {setScrollState(SCROLL_STATE_IDLE);}resetScroll();} break;}//...return true;
}
无论是滚动还是fling,最终都会调用LinearLayoutManager的scrollVerticallyBy方法。
@Override
public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler,RecyclerView.State state) {//...return scrollBy(dy, recycler, state);
}
int scrollBy(int delta, RecyclerView.Recycler recycler, RecyclerView.State state) {if (getChildCount() == 0 || delta == 0) {return 0;}ensureLayoutState();mLayoutState.mRecycle = true;//delta > 0,向下填充,layoutDirection为LayoutState.LAYOUT_ENDfinal int layoutDirection = delta > 0 ? LayoutState.LAYOUT_END : LayoutState.LAYOUT_START;final int absDelta = Math.abs(delta);//注释0处,更新布局信息。updateLayoutState(layoutDirection, absDelta, true, state);//注释1处,注意,这里继续调用fill方法填充子View。final int consumed = mLayoutState.mScrollingOffset+ fill(recycler, mLayoutState, state, false);if (consumed < 0) {//return 0;}final int scrolled = absDelta > consumed ? layoutDirection * consumed : delta;//注释2处,偏移所有的子ViewmOrientationHelper.offsetChildren(-scrolled);if (DEBUG) {Log.d(TAG, "scroll req: " + delta + " scrolled: " + scrolled);}mLayoutState.mLastScrollDelta = scrolled;return scrolled;
}
scrollBy 方法的注释1处,注意,这里继续调用fill方法填充子View。这里现在涉及到到了 ViewHodler的回收和复用哟。关于回收和复用的细节,下篇文章分析。
注释2处,偏移所有的子View,保证RecyclerView的第一个子View的top坐标就是RecyclerView的top坐标减去RecyclerView的paddingTop。