主要方法
构造函数
- 进行View相关配置属性设置,触摸范围、滑动速度等
- 设置Item动画监听器
- 初始化AdapterManager,创建AdapterHelper(负责Adapter里的数据集发生变化时的预处理操作)
- 初始化ChildHelper(负责管理和访问 RecyclerView 的子视图)
- 如果配置了LayoutManager 则通过反射方法创建它
1
public RecyclerView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); //设置为滚动容器 setScrollContainer(true); setFocusableInTouchMode(true); //View配置相关属性设置 final ViewConfiguration vc = ViewConfiguration.get(context); mTouchSlop = vc.getScaledTouchSlop(); mScaledHorizontalScrollFactor = ViewConfigurationCompat.getScaledHorizontalScrollFactor(vc, context); mScaledVerticalScrollFactor = ViewConfigurationCompat.getScaledVerticalScrollFactor(vc, context); mMinFlingVelocity = vc.getScaledMinimumFlingVelocity(); mMaxFlingVelocity = vc.getScaledMaximumFlingVelocity(); setWillNotDraw(getOverScrollMode() == View.OVER_SCROLL_NEVER); // 设置Item动画监听器 mItemAnimator.setListener(mItemAnimatorListener); // 设置 AdapterManager initAdapterManager(); // 设置 ChildrenHelper initChildrenHelper(); initAutofill(); // If not explicitly specified this view is important for accessibility. // 硬件加速相关属性设置 if (ViewCompat.getImportantForAccessibility(this) == ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_AUTO) { ViewCompat.setImportantForAccessibility(this, ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES); } mAccessibilityManager = (AccessibilityManager) getContext() .getSystemService(Context.ACCESSIBILITY_SERVICE); setAccessibilityDelegateCompat(new RecyclerViewAccessibilityDelegate(this)); //初始化attrs TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.RecyclerView, defStyleAttr, 0); if (Build.VERSION.SDK_INT >= 29) { saveAttributeDataForStyleable(context, R.styleable.RecyclerView, attrs, a, defStyleAttr, 0); } String layoutManagerName = a.getString(R.styleable.RecyclerView_layoutManager); int descendantFocusability = a.getInt( R.styleable.RecyclerView_android_descendantFocusability, -1); if (descendantFocusability == -1) { setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS); } mClipToPadding = a.getBoolean(R.styleable.RecyclerView_android_clipToPadding, true); mEnableFastScroller = a.getBoolean(R.styleable.RecyclerView_fastScrollEnabled, false); if (mEnableFastScroller) { StateListDrawable verticalThumbDrawable = (StateListDrawable) a .getDrawable(R.styleable.RecyclerView_fastScrollVerticalThumbDrawable); Drawable verticalTrackDrawable = a .getDrawable(R.styleable.RecyclerView_fastScrollVerticalTrackDrawable); StateListDrawable horizontalThumbDrawable = (StateListDrawable) a .getDrawable(R.styleable.RecyclerView_fastScrollHorizontalThumbDrawable); Drawable horizontalTrackDrawable = a .getDrawable(R.styleable.RecyclerView_fastScrollHorizontalTrackDrawable); initFastScroller(verticalThumbDrawable, verticalTrackDrawable, horizontalThumbDrawable, horizontalTrackDrawable); } a.recycle(); // 反射方法创建 LayoutManager // Create the layoutManager if specified. createLayoutManager(context, layoutManagerName, attrs, defStyleAttr, 0); boolean nestedScrollingEnabled = true; if (Build.VERSION.SDK_INT >= 21) { // SDK >=21下 ,nestedScrollingEnabled状态支持变更 a = context.obtainStyledAttributes(attrs, NESTED_SCROLLING_ATTRS, defStyleAttr, 0); if (Build.VERSION.SDK_INT >= 29) { saveAttributeDataForStyleable( context, NESTED_SCROLLING_ATTRS, attrs, a, defStyleAttr, 0); } nestedScrollingEnabled = a.getBoolean(0, true); a.recycle(); } // 重置nestedScrollingEnabled状态 SDK 21以下默认true setNestedScrollingEnabled(nestedScrollingEnabled); }
setLayoutManager
- 处理重新设置一个新的LayoutManager的一些逻辑
- 设置this给到LayoutManager,并如果attach了则执行LayoutManger的attach分发实践
- 更新缓存大小,并请求重新布局
1 | public void setLayoutManager(@Nullable LayoutManager layout) { //过滤LayoutManager if (layout == mLayout) { return; } //停止滚动 stopScroll(); //mLayout有值,则进行mLayout的解除关联、销毁操作 if (mLayout != null) { // end all running animations 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(); } // 对有缺陷的item animator一个防御措施 mChildHelper.removeAllViewsUnfiltered(); // 重新赋值 mLayout = layout; if (layout != null) { //如果layout已经关联一个Recyclerview对象,则抛出异常 if (layout.mRecyclerView != null) { throw new IllegalArgumentException("LayoutManager " + layout + " is already attached to a RecyclerView:" + layout.mRecyclerView.exceptionLabel()); } //LayoutManager关联当前RecyclerView mLayout.setRecyclerView(this); if (mIsAttached) { mLayout.dispatchAttachedToWindow(this); } } //重置Recycler的mCachedViews中的ViewHolder,并把其加入RecycledViewPool mRecycler.updateViewCacheSize(); //请求刷新Layout requestLayout(); } |
setAdapter
- 解除frozen状态
- 设置新的Adapter,并触发一系列监听事件
1 | public void setAdapter(@Nullable Adapter adapter) { // bail out if layout is frozen setLayoutFrozen(false); setAdapterInternal(adapter, false, true); processDataSetCompletelyChanged(false); requestLayout(); } |
setAdapter和swapAdapter实现方法相同,传的参数不同。 看看setAdapterInternal方法的传参
- compatibleWithPrevious: 设置为true则表示新的Adapter和老的Adapter使用相同的ViewHolder和itemType(可以避免缓存失效)
- removeAndRecycleViews: 如果为true,将会删除并回收所有现有的视图。如果compatibleWithPrevious为false,则忽略此参数。
1 | private void setAdapterInternal(@Nullable Adapter adapter, boolean compatibleWithPrevious,boolean removeAndRecycleViews) { //如果原先Adapter不为null,则解除关联 if (mAdapter != null) { mAdapter.unregisterAdapterDataObserver(mObserver); mAdapter.onDetachedFromRecyclerView(this); } //compatibleWithPrevious为false或者removeAndRecycleViewstruetrue则移除所有View if (!compatibleWithPrevious || removeAndRecycleViews) { removeAndRecycleViews(); } //AdapterHelper类reset mAdapterHelper.reset(); //重新赋值新的Adapter给mAdapter并建立关联 final Adapter oldAdapter = mAdapter; mAdapter = adapter; if (adapter != null) { adapter.registerAdapterDataObserver(mObserver); adapter.onAttachedToRecyclerView(this); } //LayoutManager进行Adapter更换 if (mLayout != null) { mLayout.onAdapterChanged(oldAdapter, mAdapter); } //Recycler进行Adapter更换,并传入compatibleWithPrevious mRecycler.onAdapterChanged(oldAdapter, mAdapter, compatibleWithPrevious); mState.mStructureChanged = true; } |
processDataSetCompletelyChanged
方法 -> markKnownViewsInvalid
(将所有已知ViewHolder标志为无效) -> markItemDecorInsetsDirty
(将当前的ChildView以及Recycler中的ChildView的mInsetsDirty设置为true)、mRecycler.markKnownViewsInvalid
(从Recycler的mCachedViews中的ViewHolder标志位无效) -> recycleAndClearCachedViews
(从mCachedViews中的ViewHolder移除添加进RecycledViewPool)
扩展方法
addOnChildAttachStateChangeListener、removeOnChildAttachStateChangeListener、clearOnChildAttachStateChangeListeners
监听子View的添加和释放,官方推荐使用过重的View资源可以在这个监听器里进行释放
1 | public interface OnChildAttachStateChangeListener { void onChildViewAttachedToWindow(@NonNull View view); void onChildViewDetachedFromWindow(@NonNull View view); } |
setOnFlingListener、getOnFlingListener
监听RecyclerView的快速滑动的事件,可以获取横向滑动或者纵向滑动的速度,并且可以进行拦截处理。
1 | public abstract static class OnFlingListener { public abstract boolean onFling(int velocityX, int velocityY); } |
setRecycledViewPool(@Nullable RecycledViewPool pool)、getRecycledViewPool
如果有多个相同数据类型的Adapter,可以设置RecycledViewPool共享池。
setViewCacheExtension(@Nullable ViewCacheExtension extension)
自定义ViewCacheExtension
setItemViewCacheSize(int size)
设置加入RecycledViewPool之前可缓存的数量
addItemDecoration(@NonNull ItemDecoration decor, int index)、addItemDecoration(@NonNull ItemDecoration decor)、getItemDecorationAt(int index)、getItemDecorationCount()、removeItemDecorationAt(int index)、removeItemDecoration(@NonNull ItemDecoration decor)
添加ItemDecoration, ItemDecoration有层级关系, index值会影响ItemDecoration所在层级。index为-1,则添加到最后。
setChildDrawingOrderCallback(@Nullable ChildDrawingOrderCallback childDrawingOrderCallback)
可用来更改RecyclerView子项的绘制顺序
1 | public interface ChildDrawingOrderCallback { int onGetChildDrawingOrder(int childCount, int i); } |
addOnScrollListener(@NonNull OnScrollListener listener)、removeOnScrollListener(@NonNull OnScrollListener listener)、clearOnScrollListeners()
用来监听RecyclerView的滚动状态和滚动距离
1 | public abstract static class OnScrollListener { public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState){} public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy){} } |
scrollToPosition(int position)
滚动到指定位置
smoothScrollToPosition(int position)
带动画滚动到指定位置
setEdgeEffectFactory(@NonNull EdgeEffectFactory edgeEffectFactory)、getEdgeEffectFactory()
自定义边界UI
addOnItemTouchListener(@NonNull OnItemTouchListener listener)、removeOnItemTouchListener(@NonNull OnItemTouchListener listener)
处理Item触摸事件
1 | public interface OnItemTouchListener { boolean onInterceptTouchEvent(@NonNull RecyclerView rv, @NonNull MotionEvent e); void onTouchEvent(@NonNull RecyclerView rv, @NonNull MotionEvent e); void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept); } |
View绘制三大流程
measure
1 | @Override protected void onMeasure(int widthSpec, int heightSpec) { //第一种情况当前LayoutManager为null if (mLayout == null) { defaultOnMeasure(widthSpec, heightSpec); return; } //第二种情况LayoutManager开启了自动测量 if (mLayout.isAutoMeasureEnabled()) { final int widthMode = MeasureSpec.getMode(widthSpec); final int heightMode = MeasureSpec.getMode(heightSpec); //调用LayoutManager的onMeasure方法进行测量。 mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec); final boolean measureSpecModeIsExactly = widthMode == MeasureSpec.EXACTLY && heightMode == MeasureSpec.EXACTLY; if (measureSpecModeIsExactly || mAdapter == null) { return; } //mLayoutStep为State.STEP_START执行dispatchLayoutStep1 if (mState.mLayoutStep == State.STEP_START) { dispatchLayoutStep1(); } mLayout.setMeasureSpecs(widthSpec, heightSpec); mState.mIsMeasuring = true; //执行dispatchLayoutStep2 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 { //第三种情况 LayoutManager没有开启自动测量 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 } } |
第一种情况
直接调用defaultOnMeasure方法
1 | void defaultOnMeasure(int widthSpec, int heightSpec) { // calling LayoutManager here is not pretty but that API is already public and it is better // than creating another method since this is internal. final int width = LayoutManager.chooseSize(widthSpec, getPaddingLeft() + getPaddingRight(), ViewCompat.getMinimumWidth(this)); final int height = LayoutManager.chooseSize(heightSpec, getPaddingTop() + getPaddingBottom(), ViewCompat.getMinimumHeight(this)); setMeasuredDimension(width, height); } |
直接调用LayoutManager.chooseSize来获取宽高,然后直接setMeasuredDimension
1 | //通过RecyclerView的测量mode来获取不同的值 public static int chooseSize(int spec, int desired, int min) { final int mode = View.MeasureSpec.getMode(spec); final int size = View.MeasureSpec.getSize(spec); switch (mode) { case View.MeasureSpec.EXACTLY: return size; case View.MeasureSpec.AT_MOST: return Math.min(size, Math.max(desired, min)); case View.MeasureSpec.UNSPECIFIED: default: return Math.max(desired, min); } } |
第二种情况 当LayoutManager开启了自动测量
dispatchLayoutStep1
- 第一步layout方法
- 处理adapter变更
- 确定需要执行的动画
- 针对当前的Views进行信息缓存
- 如有必要,则进行预布局并缓存信息
1 | 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) { // 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); } } } 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(); } else { clearOldPositions(); } onExitLayoutOrScroll(); stopInterceptRequestLayout(false); mState.mLayoutStep = State.STEP_LAYOUT; } |
1 | private void processAdapterUpdatesAndSetAnimationFlags() { if (mDataSetHasChangedAfterLayout) { // Processing these items have no value since data set changed unexpectedly. // Instead, we just reset it. mAdapterHelper.reset(); if (mDispatchItemsChangedEvent) { 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(); } |
dispatchLayoutStep2
1 | private void dispatchLayoutStep2() { startInterceptRequestLayout(); onEnterLayoutOrScroll(); mState.assertLayoutStep(State.STEP_LAYOUT | State.STEP_ANIMATIONS); mAdapterHelper.consumeUpdatesInOnePass(); mState.mItemCount = mAdapter.getItemCount(); mState.mDeletedInvisibleItemCountSincePreviousLayout = 0; mState.mInPreLayout = false; //调用LayoutManager的onLayoutChildren对children进行测量和布局 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; //设置mLayoutStep为State.STEP_ANIMATIONS mState.mLayoutStep = State.STEP_ANIMATIONS; onExitLayoutOrScroll(); stopInterceptRequestLayout(false); } |
没有开启自动测量
- 如果mHasFixedSize为true(也就是调用了setHasFixedSize方法),将直接调用LayoutManager的onMeasure方法进行测量。
- 如果mHasFixedSize为false,同时此时如果有数据更新,先处理数据更新的事务,然后调用LayoutManager的onMeasure方法进行测量
layout
1 | @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { TraceCompat.beginSection(TRACE_ON_LAYOUT_TAG); dispatchLayout(); TraceCompat.endSection(); mFirstLayoutComplete = true; } |
dispatchLayout
这个方法保证RecyclerView必须经历三个过程–dispatchLayoutStep1、dispatchLayoutStep2、dispatchLayoutStep3。
1 | void dispatchLayout() { //这边如果mAdapter为null,就不进行展示了 if (mAdapter == null) { Log.e(TAG, "No adapter attached; skipping layout"); // leave the state in START return; } //这边如果mLayout为null,就不进行展示了 if (mLayout == null) { Log.e(TAG, "No layout manager attached; skipping layout"); // leave the state in START return; } //更改mState.mIsMeasuring mState.mIsMeasuring = false; //如果mState.mLayoutStep为State.STEP_START再次调用dispatchLayoutStep1和dispatchLayoutStep2 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 dispatchLayoutStep3(); } |
dispatchLayoutStep3
这是最后一步
1 | private void dispatchLayoutStep3() { mState.assertLayoutStep(State.STEP_ANIMATIONS); startInterceptRequestLayout(); onEnterLayoutOrScroll(); //重新将mState.mLayoutStep的值赋值为State.STEP_START,保证下次dispatchLayout继续走3步 mState.mLayoutStep = State.STEP_START; 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 { //ItemHolderInfo中保存ItemView的位置信息 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); } mLayout.removeAndRecycleScrapInt(mRecycler); mState.mPreviousLayoutItemCount = mState.mItemCount; mDataSetHasChangedAfterLayout = false; mDispatchItemsChangedEvent = 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(); stopInterceptRequestLayout(false); mViewInfoStore.clear(); if (didChildRangeChange(mMinMaxLayoutPositions[0], mMinMaxLayoutPositions[1])) { dispatchOnScrolled(0, 0); } recoverFocusFromState(); resetFocusInfo(); } |
RecyclerView跟其他ViewGroup不同的地方在于,如果开启了自动测量,在measure阶段,已经将Children布局完成了;如果没有开启自动测量,则在layout阶段才布局Children
在LayoutManager中的绘制
LinearLayoutManager#onLayoutChildren
1 | @Override public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) { // ... // 找到锚点(具体过程等到分析 layout 时再说) // (1) detachAndScrapAttachedViews(recycler); if (mAnchorInfo.mLayoutFromEnd) { // ... } else { // (2) fill(recycler, mLayoutState, state, false); // ... } // ... } |
首先看(1)处,detachAndScrapAttachedViews 方法会根据情况将子 View 回收到相应缓存,具体过程之后再看,由于现在是第一次 layout,RecyclerView 中没有子 View,所以现在该方法没啥用。
接下来看(2)处,这里的 fill 方法比较重要,它的作用是填充布局。看一下该方法
LinearLayoutManager#fill
1 | int fill(RecyclerView.Recycler recycler, LayoutState layoutState, RecyclerView.State state, boolean stopOnFocusable) { // 进行 layout 时 layoutState.mScrollingOffset 的值被设置为 // LayoutState.SCROLLING_OFFSET_NaN,不会进入此 if 块 if (layoutState.mScrollingOffset != LayoutState.SCROLLING_OFFSET_NaN) { // ... recycleByLayoutState(recycler, layoutState); } // 需要填充的空间 int remainingSpace = layoutState.mAvailable + layoutState.mExtra; // 还有需要填充的空间并且 item 数未满 while ((layoutState.mInfinite || remainingSpace > 0) && layoutState.hasMore(state)) { // ... // (1) layoutChunk(recycler, state, layoutState, layoutChunkResult); // 计算剩余空间 // 同上,在 layout 时不会进入 if 块中 if (layoutState.mScrollingOffset != LayoutState.SCROLLING_OFFSET_NaN) { // ... recycleByLayoutState(recycler, layoutState); } // ... } } |
LinearLayoutManager#layoutChunk
1 | void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state, LayoutState layoutState, LayoutChunkResult result) { // (1) View view = layoutState.next(recycler); // ... // 默认情况下,layoutState.mScrapList 等于 null if (layoutState.mScrapList == null) { if (mShouldReverseLayout == (layoutState.mLayoutDirection == LayoutState.LAYOUT_START)) { // (2) addView(view); } else { addView(view, 0); } } else { // ... } } |
(2)处的 addView 方法就不多说了,该方法将得到的子 View 添加到 RecyclerView 中。主要看(1)处,看看子 View 从何而来
1 | View next(RecyclerView.Recycler recycler) { // ... final View view = recycler.getViewForPosition(mCurrentPosition); return view; } |
这个方法是不是很熟悉呢?没错,它就是之前分析的 Recycler 的 getViewForPosition 方法。
不过由于现在没有任何缓存,所以第一次 layout 的时候是通过 Adapter 的 createViewHolder 来创建子 View的,并且没有添加任何缓存。
draw
- 调用super.draw方法。这里主要做了两件事:
- 将Children的绘制分发给ViewGroup;
- 将分割线的绘制分发给ItemDecoration。
- 如果需要的话,调用ItemDecoration的onDrawOver方法。通过这个方法,我们在每个ItemView上面画上很多东西。
- 如果RecyclerView调用了setClipToPadding,会实现一种特殊的滑动效果–每个ItemView可以滑动到padding区域。
1 | @Override public void draw(Canvas c) { //第一步 super.draw(c); //第二步 final int count = mItemDecorations.size(); for (int i = 0; i < count; i++) { mItemDecorations.get(i).onDrawOver(c, this, mState); } // 第三步 // TODO If padding is not 0 and clipChildrenToPadding is false, to draw glows properly, we // need find children closest to edges. Not sure if it is worth the effort. boolean needsInvalidate = false; if (mLeftGlow != null && !mLeftGlow.isFinished()) { final int restore = c.save(); final int padding = mClipToPadding ? getPaddingBottom() : 0; c.rotate(270); c.translate(-getHeight() + padding, 0); needsInvalidate = mLeftGlow != null && mLeftGlow.draw(c); c.restoreToCount(restore); } if (mTopGlow != null && !mTopGlow.isFinished()) { final int restore = c.save(); if (mClipToPadding) { c.translate(getPaddingLeft(), getPaddingTop()); } needsInvalidate |= mTopGlow != null && mTopGlow.draw(c); c.restoreToCount(restore); } if (mRightGlow != null && !mRightGlow.isFinished()) { final int restore = c.save(); final int width = getWidth(); final int padding = mClipToPadding ? getPaddingTop() : 0; c.rotate(90); c.translate(-padding, -width); needsInvalidate |= mRightGlow != null && mRightGlow.draw(c); c.restoreToCount(restore); } if (mBottomGlow != null && !mBottomGlow.isFinished()) { final int restore = c.save(); c.rotate(180); if (mClipToPadding) { c.translate(-getWidth() + getPaddingRight(), -getHeight() + getPaddingBottom()); } else { c.translate(-getWidth(), -getHeight()); } needsInvalidate |= mBottomGlow != null && mBottomGlow.draw(c); c.restoreToCount(restore); } // If some views are animating, ItemDecorators are likely to move/change with them. // Invalidate RecyclerView to re-draw decorators. This is still efficient because children's // display lists are not invalidated. if (!needsInvalidate && mItemAnimator != null && mItemDecorations.size() > 0 && mItemAnimator.isRunning()) { needsInvalidate = true; } if (needsInvalidate) { ViewCompat.postInvalidateOnAnimation(this); } } |
关于Children的绘制和ItemDecoration的绘制,是在onDraw方法里面
1 | @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); } } |
缓存机制
四级缓存
ViewHolder的几个状态值
mChangedScrap和mAttachedScrap的区别
首先,如果调用了Adapter的notifyItemChanged方法,会重新回调到LayoutManager的onLayoutChildren方法里面,而在onLayoutChildren方法里面,会将屏幕上所有的ViewHolder回收到mAttachedScrap和mChangedScrap。这个过程就是将ViewHolder分别放到mAttachedScrap和mChangedScrap,而什么条件下放在mAttachedScrap,什么条件放在mChangedScrap,这个就是他们俩的区别。
1 | void scrapView(View view) { final ViewHolder holder = getChildViewHolderInt(view); //1.被同时标记为remove或invalid;2.完全没有改变的ViewHolder。这里还有第三个判断,这个跟RecyclerView的ItemAnimator有关,如果ItemAnimator为空或者ItemAnimator的canReuseUpdatedViewHolder方法为true,也会放入到mAttachedScrap。 if (holder.hasAnyOfTheFlags(ViewHolder.FLAG_REMOVED | ViewHolder.FLAG_INVALID) || !holder.isUpdated() || canReuseUpdatedViewHolder(holder)) { if (holder.isInvalid() && !holder.isRemoved() && !mAdapter.hasStableIds()) { throw new IllegalArgumentException("Called scrap view with an invalid view." + " Invalid views cannot be reused from scrap, they should rebound from" + " recycler pool." + exceptionLabel()); } holder.setScrapContainer(this, false); mAttachedScrap.add(holder); } else { //ViewHolder的isUpdated方法返回为true时,会放入到mChangedScrap里面去。 if (mChangedScrap == null) { mChangedScrap = new ArrayList<ViewHolder>(); } holder.setScrapContainer(this, true); mChangedScrap.add(holder); } } |
复用
RecyclerView对ViewHolder的复用,我们得从LayoutState的next方法开始。LayoutManager在布局itemView时,需要获取一个ViewHolder对象,就是通过这个方法来获取,具体的复用逻辑也是在这个方面开始调用的。
1 | View next(RecyclerView.Recycler recycler) { final View view = recycler.getViewForPosition(mCurrentPosition); mCurrentPosition += mItemDirection; return view; } |
再来看Recycler的getViewForPosition
1 | View getViewForPosition(int position, boolean dryRun) { return tryGetViewHolderForPositionByDeadline(position, dryRun, FOREVER_NS).itemView; } |
最后走到tryGetViewHolderForPositionByDeadline这个方法,RecyclerView真正复用的核心就在这个方法
1 | @Nullable ViewHolder tryGetViewHolderForPositionByDeadline(int position, boolean dryRun, long deadlineNs) { //position和mState.getItemCount对不上就会抛出异常 if (position < 0 || position >= mState.getItemCount()) { throw new IndexOutOfBoundsException("Invalid item position " + position + "(" + position + "). Item count:" + mState.getItemCount() + exceptionLabel()); } boolean fromScrapOrHiddenOrCache = false; ViewHolder holder = null; // 0) If there is a changed scrap, try to find from there //如果当前是预布局阶段,那么就从mChangedScrap里面去获取ViewHolder if (mState.isPreLayout()) { holder = getChangedScrapViewForPosition(position); fromScrapOrHiddenOrCache = holder != null; } // 1) Find by position from scrap/hidden list/cache //如果holder为null,分别从mAttachedScrap、 mHiddenViews、mCachedViews获取ViewHolder if (holder == null) { holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun); if (holder != null) { //检查ViewHolder是否有效 if (!validateViewHolderForOffsetPosition(holder)) { // recycle holder (and unscrap if relevant) since it can't be used //做一些清理操作,然后重新放入到缓存里面 if (!dryRun) { // we would like to recycle this but need to make sure it is not used by // animation logic etc. holder.addFlags(ViewHolder.FLAG_INVALID); if (holder.isScrap()) { removeDetachedView(holder.itemView, false); holder.unScrap(); } else if (holder.wasReturnedFromScrap()) { holder.clearReturnedFromScrapFlag(); } //做回收操作 recycleViewHolderInternal(holder); } holder = null; } else { fromScrapOrHiddenOrCache = true; } } } //1. 如果Adapter的hasStableIds方法返回为true,优先通过ViewType和id两个条件来寻找。如果没有找到,那么就进行第2步。 //2. 如果Adapter的hasStableIds方法返回为false,在这种情况下,首先会在ViewCacheExtension里面找,如果还没有找到的话,最后会在RecyclerViewPool里面来获取ViewHolder。 //3. 如果以上的复用步骤都没有找到合适的ViewHolder,最后就会调用Adapter的onCreateViewHolder方法来创建一个新的ViewHolder。 if (holder == null) { final int offsetPosition = mAdapterHelper.findPositionOffset(position); //校验offsetPosition if (offsetPosition < 0 || offsetPosition >= mAdapter.getItemCount()) { throw new IndexOutOfBoundsException("Inconsistency detected. Invalid item " + "position " + position + "(offset:" + offsetPosition + ")." + "state:" + mState.getItemCount() + exceptionLabel()); } final int type = mAdapter.getItemViewType(offsetPosition); // 2) Find from scrap/cache via stable ids, if exists //通过ViewType和Id来查找 if (mAdapter.hasStableIds()) { //分别从mAttachedScrap和mCachedViews数组寻找合适的ViewHolder holder = getScrapOrCachedViewForId(mAdapter.getItemId(offsetPosition), type, dryRun); if (holder != null) { // update position holder.mPosition = offsetPosition; fromScrapOrHiddenOrCache = true; } } //如果存在ViewCacheExtension,则从ViewCacheExtension中查找 这个玩意需要用户自定义,很少使用 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 (view != null) { holder = getChildViewHolder(view); if (holder == null) { throw new IllegalArgumentException("getViewForPositionAndType returned" + " a view which does not have a ViewHolder" + exceptionLabel()); } else if (holder.shouldIgnore()) { throw new IllegalArgumentException("getViewForPositionAndType returned" + " a view that is ignored. You must call stopIgnoring before" + " returning this view." + exceptionLabel()); } } } //还没找到再到RecycledViewPool中进行查找 if (holder == null) { // fallback to pool if (DEBUG) { Log.d(TAG, "tryGetViewHolderForPositionByDeadline(" + position + ") fetching from shared pool"); } holder = getRecycledViewPool().getRecycledView(type); if (holder != null) { holder.resetInternal(); if (FORCE_INVALIDATE_DISPLAY_LIST) { invalidateDisplayListInt(holder); } } } //实在没有只能通过createViewHolder进行创建 if (holder == null) { long start = getNanoTime(); if (deadlineNs != FOREVER_NS && !mRecyclerPool.willCreateInTime(type, start, deadlineNs)) { // abort - we have a deadline we can't meet return null; } holder = mAdapter.createViewHolder(RecyclerView.this, type); if (ALLOW_THREAD_GAP_WORK) { // only bother finding nested RV if prefetching RecyclerView innerView = findNestedRecyclerView(holder.itemView); if (innerView != null) { holder.mNestedRecyclerView = new WeakReference<>(innerView); } } long end = getNanoTime(); mRecyclerPool.factorInCreateTime(type, end - start); if (DEBUG) { Log.d(TAG, "tryGetViewHolderForPositionByDeadline created new ViewHolder"); } } } // This is very ugly but the only place we can grab this information // before the View is rebound and returned to the LayoutManager for post layout ops. // We don't need this in pre-layout since the VH is not updated by the LM. if (fromScrapOrHiddenOrCache && !mState.isPreLayout() && holder .hasAnyOfTheFlags(ViewHolder.FLAG_BOUNCED_FROM_HIDDEN_LIST)) { holder.setFlags(0, ViewHolder.FLAG_BOUNCED_FROM_HIDDEN_LIST); if (mState.mRunSimpleAnimations) { int changeFlags = ItemAnimator .buildAdapterChangeFlagsForAnimations(holder); changeFlags |= ItemAnimator.FLAG_APPEARED_IN_PRE_LAYOUT; final ItemHolderInfo info = mItemAnimator.recordPreLayoutInformation(mState, holder, changeFlags, holder.getUnmodifiedPayloads()); recordAnimationInfoIfBouncedHiddenView(holder, info); } } boolean bound = false; if (mState.isPreLayout() && holder.isBound()) { // do not update unless we absolutely have to. holder.mPreLayoutPosition = position; } else if (!holder.isBound() || holder.needsUpdate() || holder.isInvalid()) { if (DEBUG && holder.isRemoved()) { throw new IllegalStateException("Removed holder should be bound and it should" + " come here only in pre-layout. Holder: " + holder + exceptionLabel()); } final int offsetPosition = mAdapterHelper.findPositionOffset(position); bound = tryBindViewHolderByDeadline(holder, offsetPosition, position, deadlineNs); } final ViewGroup.LayoutParams lp = holder.itemView.getLayoutParams(); final LayoutParams rvLayoutParams; if (lp == null) { rvLayoutParams = (LayoutParams) generateDefaultLayoutParams(); holder.itemView.setLayoutParams(rvLayoutParams); } else if (!checkLayoutParams(lp)) { rvLayoutParams = (LayoutParams) generateLayoutParams(lp); holder.itemView.setLayoutParams(rvLayoutParams); } else { rvLayoutParams = (LayoutParams) lp; } rvLayoutParams.mViewHolder = holder; rvLayoutParams.mPendingInvalidate = fromScrapOrHiddenOrCache && bound; return holder; } |
从RecyclerViewPool里面获取ViewHolder
在RecyclerViewPool的内部,使用SparseArray来存储每个ViewType对应的ViewHolder数组,其中每个数组的最大size为5。
1 | static class ScrapData { final ArrayList<ViewHolder> mScrapHeap = new ArrayList<>(); int mMaxScrap = DEFAULT_MAX_SCRAP; long mCreateRunningAverageNs = 0; long mBindRunningAverageNs = 0; } SparseArray<ScrapData> mScrap = new SparseArray<>(); |
回收
scrap数组
关于ViewHolder回收到scrap数组里面,其实我在前面已经简单的分析了,重点就在于Recycler的scrapView方法里面。我们来看看scrapView在哪里被调用了。有如下两个地方:
- 在getScrapOrHiddenOrCachedHolderForPosition方法里面,如果从mHiddenViews获得一个ViewHolder的话,会先将这个ViewHolder从mHiddenViews数组里面移除,然后调用Recycler的scrapView方法将这个ViewHolder放入到scrap数组里面,并且标记FLAG_RETURNED_FROM_SCRAP和FLAG_BOUNCED_FROM_HIDDEN_LIST两个flag。
- 在LayoutManager里面的scrapOrRecycleView方法也会调用Recycler的scrapView方法。而有两种情形下会出现如此情况:1. 手动调用了LayoutManager相关的方法;2. RecyclerView进行了一次布局(调用了requestLayout方法)
mCacheViews数组
mCacheViews数组作为二级缓存,回收的路径相较于一级缓存要多。关于mCacheViews数组,重点在于Recycler的recycleViewHolderInternal方法里面。 #### mHiddenViews数组 一个ViewHolder回收到mHiddenView数组里面的条件比较简单,如果当前操作支持动画,就会调用到RecyclerView的addAnimatingView方法,在这个方法里面会将做动画的那个View添加到mHiddenView数组里面去。通常就是动画期间可以会进行复用,因为mHiddenViews只在动画期间才会有元素。
RecyclerViewPool
RecyclerViewPool跟mCacheViews,都是通过recycleViewHolderInternal方法来进行回收,所以情景与mCacheViews差不多,只不过当不满足放入mCacheViews时,才会放入到RecyclerViewPool里面去。
为什么hasStableIds方法返回true会提高效率呢?
了解了RecyclerView的复用和回收机制之后,这个问题就变得很简单了。我从两个方面来解释原因。
A. 复用方面
1 | if (mAdapter.hasStableIds()) { holder = getScrapOrCachedViewForId(mAdapter.getItemId(offsetPosition), type, dryRun); if (holder != null) { // update position holder.mPosition = offsetPosition; fromScrapOrHiddenOrCache = true; } } |
在前面通过Position方式来获取一个ViewHolder失败之后,如果Adapter的hasStableIds方法返回为true,在进行通过ViewType方式来获取ViewHolder时,会优先到1级或者二级缓存里面去寻找,而不是直接去RecyclerViewPool里面去寻找。从这里,我们可以看到,在复用方面,hasStableIds方法提高了效率。
B. 回收方面
1 | private void scrapOrRecycleView(Recycler recycler, int index, View view) { final ViewHolder viewHolder = getChildViewHolderInt(view); if (viewHolder.shouldIgnore()) { if (DEBUG) { Log.d(TAG, "ignoring view " + viewHolder); } return; } if (viewHolder.isInvalid() && !viewHolder.isRemoved() && !mRecyclerView.mAdapter.hasStableIds()) { removeViewAt(index); recycler.recycleViewHolderInternal(viewHolder); } else { detachViewAt(index); recycler.scrapView(view); mRecyclerView.mViewInfoStore.onViewDetached(viewHolder); } } |
从上面的代码中,我们可以看出,如果hasStableIds方法返回为true的话,这里所有的回收都进入scrap数组里面。这刚好与前面对应了。
Adapter源码解析
先看Adapter源码
1 | public abstract static class Adapter<VH extends ViewHolder> { private final AdapterDataObservable mObservable = new AdapterDataObservable(); private boolean mHasStableIds = false; //创建一个ViewHolder对象,主要作用是将数据保存在ViewHolder,以供后面bind操作使用 @NonNull public abstract VH onCreateViewHolder(@NonNull ViewGroup parent, int viewType); //数据绑定方法 public abstract void onBindViewHolder(@NonNull VH holder, int position); public void onBindViewHolder(@NonNull VH holder, int position, @NonNull List<Object> payloads) { onBindViewHolder(holder, position); } @NonNull public final VH createViewHolder(@NonNull ViewGroup parent, int viewType) { try { TraceCompat.beginSection(TRACE_CREATE_VIEW_TAG); final VH holder = onCreateViewHolder(parent, viewType); if (holder.itemView.getParent() != null) { throw new IllegalStateException("ViewHolder views must not be attached when" + " created. Ensure that you are not passing 'true' to the attachToRoot" + " parameter of LayoutInflater.inflate(..., boolean attachToRoot)"); } holder.mItemViewType = viewType; return holder; } finally { TraceCompat.endSection(); } } public final void bindViewHolder(@NonNull 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(); } //该方法带一个Position,主要是返回当前位置的ViewType。这个方法通常用于一个RecyclerView需要加载不同的布局。 public int getItemViewType(int position) { return 0; } //设置当前RecyclerView的ItemView是否拥有固定id,跟getItemId方法一起使用。如果设置为true,会提高RecyclerView的缓存效率。 public void setHasStableIds(boolean hasStableIds) { if (hasObservers()) { throw new IllegalStateException("Cannot change whether this adapter has " + "stable IDs while the adapter has registered observers."); } mHasStableIds = hasStableIds; } //该方法表示的意思是返回当前位置Item的id,此方法只在setHasStableIds设置为true才会生效 public long getItemId(int position) { return NO_ID; } //当前Adapter拥有数据的数量,该方法必须被重写,否则RecyclerView展示不了任何数据 public abstract int getItemCount(); public final boolean hasStableIds() { return mHasStableIds; } public void onViewRecycled(@NonNull VH holder) { } public boolean onFailedToRecycleView(@NonNull VH holder) { return false; } public void onViewAttachedToWindow(@NonNull VH holder) { } public void onViewDetachedFromWindow(@NonNull VH holder) { } public final boolean hasObservers() { return mObservable.hasObservers(); } public void registerAdapterDataObserver(@NonNull AdapterDataObserver observer) { mObservable.registerObserver(observer); } public void unregisterAdapterDataObserver(@NonNull AdapterDataObserver observer) { mObservable.unregisterObserver(observer); } public void onAttachedToRecyclerView(@NonNull RecyclerView recyclerView) { } public void onDetachedFromRecyclerView(@NonNull RecyclerView recyclerView) { } public final void notifyDataSetChanged() { mObservable.notifyChanged(); } public final void notifyItemChanged(int position) { mObservable.notifyItemRangeChanged(position, 1); } public final void notifyItemChanged(int position, @Nullable Object payload) { mObservable.notifyItemRangeChanged(position, 1, payload); } public final void notifyItemRangeChanged(int positionStart, int itemCount) { mObservable.notifyItemRangeChanged(positionStart, itemCount); } public final void notifyItemRangeChanged(int positionStart, int itemCount, @Nullable Object payload) { mObservable.notifyItemRangeChanged(positionStart, itemCount, payload); } public final void notifyItemInserted(int position) { mObservable.notifyItemRangeInserted(position, 1); } public final void notifyItemMoved(int fromPosition, int toPosition) { mObservable.notifyItemMoved(fromPosition, toPosition); } public final void notifyItemRangeInserted(int positionStart, int itemCount) { mObservable.notifyItemRangeInserted(positionStart, itemCount); } public final void notifyItemRemoved(int position) { mObservable.notifyItemRangeRemoved(position, 1); } public final void notifyItemRangeRemoved(int positionStart, int itemCount) { mObservable.notifyItemRangeRemoved(positionStart, itemCount); } } |
onCreateViewHolder
首先,我们来看一下onCreateViewHolder方法,从它的调用时机入手。 - 一级缓存:scrap数组 - 二级缓存:CachedView - 三级缓存:ViewCacheExtension - 四级缓存:RecyclerViewPool
LayoutManager会获取ViewHolder时,如果4级缓存都没有命中,就会调用Adapter的onCreateViewHolder方法来创建一个新的ViewHolder。
onBindViewHolder
在之前的tryGetViewHolderForPositionByDeadline方法中
1 | boolean bound = false; if (mState.isPreLayout() && holder.isBound()) { // do not update unless we absolutely have to. holder.mPreLayoutPosition = position; } else if (!holder.isBound() || holder.needsUpdate() || holder.isInvalid()) { if (DEBUG && holder.isRemoved()) { throw new IllegalStateException("Removed holder should be bound and it should" + " come here only in pre-layout. Holder: " + holder + exceptionLabel()); } final int offsetPosition = mAdapterHelper.findPositionOffset(position); bound = tryBindViewHolderByDeadline(holder, offsetPosition, position, deadlineNs); } |
在tryBindViewHolderByDeadline中调用Adapter的bindViewHolder
1 | private boolean tryBindViewHolderByDeadline(@NonNull ViewHolder holder, int offsetPosition, int position, long deadlineNs) { //... mAdapter.bindViewHolder(holder, offsetPosition); //... return true; } |
在执行onBindViewHolder方法前后,各自做了一些不同的操作。比如,在执行onBindViewHolder方法之前,更新了ViewHolder的mPosition属性和给ViewHolder设置了一些flag;在执行onBindViewHolder方法之后,清理了ViewHolder的payload,并且还是给ItemView的LayoutParams的mInsetsDirty属性设置为true。
1 | public final void bindViewHolder(@NonNull 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(); } |
ViewHolder的position
这里主要分析两个方法,分别是getAdapterPosition和getLayoutPosition,对应着ViewHolder内部两个成员变量mPosition和mPreLayoutPosition两个属性。
先来看下getAdapterPosition方法
1 | public final int getAdapterPosition() { if (mOwnerRecyclerView == null) { return NO_POSITION; } return mOwnerRecyclerView.getAdapterPositionFor(this); } |
别看getAdapterPosition方法比较麻烦,还调用了RecyclerView的getAdapterPositionFor方法进行位置的计算。但是它表达的意思是非常简单的,就是获取当前ViewHolder所绑定ItemView的真实位置。这里的真实位置说的比较笼统,这样来解释吧,当我们remove掉为position为0的item,正常来说,后面ViewHolder的position应该都减1。但是RecyclerView处理Adapter的更新采用的延迟处理策略,所以在正式处理之前获取ViewHolder的位置可能会出现误差,介于这个原因,getAdapterPosition方法就出现了。 getAdapterPosition方法是怎样保证每次计算都是正确的呢?包括在正式处理之前呢?我们知道,在RecyclerView中,延迟处理的实现是在notify阶段往一个叫mPendingUpdates数组里面添加Operation,分别在dispatchLayoutStep1阶段或者dispatchLayoutStep2阶段进行处理。通过追踪getAdapterPositionFor方法,我们知道getAdapterPosition方法在计算位置时,考虑到mPendingUpdates数组的存在,所以在notify阶段和dispatchLayoutStep1阶段之间(这里假设dispatchLayoutStep1就会处理),getAdapterPosition方法返回正确的位置。
再来看看getLayoutPosition方法
1 | public final int getLayoutPosition() { return mPreLayoutPosition == NO_POSITION ? mPosition : mPreLayoutPosition; } |
getLayoutPosition方法返回的是mPosition或者mPreLayoutPosition,但是在dispatchLayoutStep1阶段之前,还未更新每个ViewHolder的position,所以获得不一定的是正确(只有在处理mPendingUpdates的操作时,position才会被更新,对应着的代码就是执行AdapterHelper$Callback接口的方法)。 但是getLayoutPosition方法为什么还有存在的必要呢?我们发现getLayoutPosition方法不会每次都计算,也就是说,getLayoutPosition方法的效率比getAdapterPosition方法高。当我们在Adapter这种调用方法来获取ViewHolder的位置时,可以优先考虑getLayoutPosition方法,因为Adapter的方法回调阶段不在mPendingUpdates处理之前,所以此时getLayoutPosition方法跟getAdapterPosition方法没有任何区别了。 但是需要注意,如果我们在其他地方获取ViewHolder的position,要特别注意这种情况,因为其他地方不能保证与RecyclerView状态同步,这种情况为了保证结果的正确性,我们应该优先考虑getAdapterPosition方法。
notifyDataSetChanged
该方法最终调用了 RecyclerViewDataObserver 的 onChanged 方法
1 | @Override public void onChanged() { // ... // 该方法主要做了这两件事 // 1. 给所有 ViewHolder 添加了 FLAG_UPDATE 和 FLAG_INVALID // 2. 默认情况下(mHasStableIds 为 false)清空 CacheViews processDataSetCompletelyChanged(true); if (!mAdapterHelper.hasPendingUpdates()) { // 进行视图重绘 requestLayout(); } } |
该方法会进行视图重绘,又来到了 layout 过程,继续以 LinearLayoutManager 为例,从它的 onLayoutChildren 方法看起,由于分析第一次 layout 时已经看过一遍了,这次主要看下不同之处:
1 | @Override public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) { // ... detachAndScrapAttachedViews(recycler); // ... } |
主要区别在于 detachAndScrapAttachedViews 方法,这次它开始起作用了,该方法在 RecyclerView 的 LayoutManager 中定义,看下它的实现:
LayoutManager#detachAndScrapAttachedViews
1 | public void detachAndScrapAttachedViews(@NonNull Recycler recycler) { final int childCount = getChildCount(); for (int i = childCount - 1; i >= 0; i--) { final View v = getChildAt(i); scrapOrRecycleView(recycler, i, v); } } |
由于不是第一次 layout,RecyclerView 这时已经有子 View 了,该方法遍历子 View,调用 scrapOrRecycleView 方法:
1 | private void scrapOrRecycleView(Recycler recycler, int index, View view) { final ViewHolder viewHolder = getChildViewHolderInt(view); // 不能回收添加了 FLAG_IGNORE 标记的 ViewHolder // 可通过 LayoutManager 的 ignoreView 为相应的 View 添加该标记 if (viewHolder.shouldIgnore()) { return; } // 这些条件都满足,进入 if 块 if (viewHolder.isInvalid() && !viewHolder.isRemoved() && !mRecyclerView.mAdapter.hasStableIds()) { removeViewAt(index); recycler.recycleViewHolderInternal(viewHolder); } else { // ... } } |
这里将子 View 移除并通过 Recycler 的 recycleViewHolderInternal 方法进行回收
Recycler#recycleViewHolderInternal
1 | void recycleViewHolderInternal(ViewHolder holder) { // ... boolean cached = false; boolean recycled = false; if (forceRecycle || holder.isRecyclable()) { // 由于此时的 ViewHolder 有 FLAG_INVALID 标记,不会进入此 if 块 if (mViewCacheMax > 0 && !holder.hasAnyOfTheFlags(ViewHolder.FLAG_INVALID | ViewHolder.FLAG_REMOVED | ViewHolder.FLAG_UPDATE | ViewHolder.FLAG_ADAPTER_POSITION_UNKNOWN)) { //... } // cached 仍为 false,进入此 if 块 if (!cached) { // 通过 RecycledViewPool 的 putRecycledView 方法缓存该 ViewHolder addViewHolderToRecycledViewPool(holder, true); recycled = true; } } // ... } |
最终被移除的子 View 缓存到了 RecycledViewPool 中。
后面在调用 fill 方法进行布局填充时,就可以从 RecycledViewPool 中拿取缓存的 View。
notifyItemChanged
该方法传入一个 int 参数,表示要数据有更新的 item 的 position。
1 | public final void notifyItemChanged(int position) { mObservable.notifyItemRangeChanged(position, 1); } |
最终调用 RecyclerViewDataObserver 的 onItemRangeChanged 方法
1 | @Override public void onItemRangeChanged(int positionStart, int itemCount, Object payload) { // 会在 mAdapterHelper 中创建一个 UpdateOp,将信息保存起来 if (mAdapterHelper.onItemRangeChanged(positionStart, itemCount, payload)) { // 如果可以进行更新操作,执行该方法 triggerUpdateProcessor(); } } |
继续看 triggerUpdateProcessor 方法
1 | void triggerUpdateProcessor() { // 判断条件默认为 false,执行 else 块 if (POST_UPDATES_ON_ANIMATION && mHasFixedSize && mIsAttached) { // ... } else { mAdapterUpdateDuringMeasure = true; requestLayout(); } } |
在保存了一些信息后,还是进行视图重绘。来到了 layout 过程后,还是以 LinearLayoutManager 为例,这次先看下布局过程的 step1,也就是 dispatchLayoutStep1 方法
1 | private void dispatchLayoutStep1() { // ... processAdapterUpdatesAndSetAnimationFlags(); // ... } |
主要看 processAdapterUpdatesAndSetAnimationFlags 方法,从名字也可以看出,它负责更新 adapter 的信息
1 | private void processAdapterUpdatesAndSetAnimationFlags() { // ... if (predictiveItemAnimationsEnabled()) { mAdapterHelper.preProcess(); } else { mAdapterHelper.consumeUpdatesInOnePass(); } // ... } |
这里借助了 mAdapterHelper,它最终又通过接口回调(回调了 markViewHoldersUpdated 方法)调用了 RecyclerView 的 viewRangeUpdate 方法
1 | void viewRangeUpdate(int positionStart, int itemCount, Object payload) { // ... for (int i = 0; i < childCount; i++) { // ... if (holder.mPosition >= positionStart && holder.mPosition < positionEnd) { // (1) holder.addFlags(ViewHolder.FLAG_UPDATE); // ... } } } |
该方法就是遍历所有子 View,找到所有发生了改变的子 View,进行相关操作。这里重点看注释(1),为改变的 ViewHolder 添加了 FLAG_UPDATE 标记。先记住这点,在后面会用到。
接下来看 onLayoutChildren 方法,和 notifyDataSetChanged 一样,主要的不同之处也是在于 detachAndScrapAttachedViews 方法,该方法遍历子 View,调用 scrapOrRecycleView 方法,下面看一下该方法
LayoutManager#scrapOrRecycleView
1 | private void scrapOrRecycleView(Recycler recycler, int index, View view) { final ViewHolder viewHolder = getChildViewHolderInt(view); // ... // 这次 ViewHolder 没有添加 FLAG_INVALID 标记,进入 else 块 if (viewHolder.isInvalid() && !viewHolder.isRemoved() && !mRecyclerView.mAdapter.hasStableIds()) { // ... } else { detachViewAt(index); recycler.scrapView(view); mRecyclerView.mViewInfoStore.onViewDetached(viewHolder); } } |
这里就和 notifyDataSetChanged 时不一样了,由于在视图重绘前没有给 ViewHolder 添加 FLAG_INVALID 标记,这次进入的是 else 块。
首先将 View 从 RecyclerView 中 detach 掉(而不是 remove 掉)。然后在回收时,调用的是 Recycler 的 scrapView 方法。该方法在前面也分析过了,这里再看一次
1 | void scrapView(View view) { final ViewHolder holder = getChildViewHolderInt(view); // 满足这几个条件中的一个就可以进入 if 循环 // 1. ViewHolder 设置了 FLAG_REMOVED 或 FLAG_INVALID // 2. ViewHolder 没有设置 FLAG_UPDATE // 3. 没有设置动画或者动画可以重用该 ViewHolder if (holder.hasAnyOfTheFlags(ViewHolder.FLAG_REMOVED | ViewHolder.FLAG_INVALID) || !holder.isUpdated() || canReuseUpdatedViewHolder(holder)) { // ... mAttachedScrap.add(holder); } // 不满足上述任意一个条件时,将 View 缓存到 mChangedScrap 中 else { if (mChangedScrap == null) { mChangedScrap = new ArrayList<ViewHolder>(); } holder.setScrapContainer(this, true); mChangedScrap.add(holder); } } |
重点看判断里面的条件 2,从前面的分析可以得知,对于发生改变的 ViewHolder,给它设置了 FLAG_UPDATE,所以它现在三个条件都不满足,进入 else 块,而对于其他的 ViewHolder,由于没有设置 FLAG_UPDATE,所以满足条件 2,进入 if 循环。
所以通过 notifyItemChanged 方法更新列表时,发生了改变的子 View 将被缓存到 ChangedScrap 中,而没有发生改变的子 View 则缓存到 AttachedScrap 中,之后通过填充布局的时候对于不同 item 就可以从相应的 Scrap 缓存中得到子 View。
另外,Scrap 缓存只作用于布局阶段,在 layout 的 step3 中将会清空 mAttachedScrap 和 mChangedScrap。