publicvoidsetLayoutManager(@Nullable LayoutManager layout){ if (layout == mLayout) { return; } stopScroll(); // TODO We should do this switch a dispatchLayout pass and animate children. There is a good // chance that LayoutManagers will re-use views. 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(); } // this is just a defensive measure for faulty item animators. mChildHelper.removeAllViewsUnfiltered(); mLayout = layout; if (layout != null) { if (layout.mRecyclerView != null) { thrownew IllegalArgumentException("LayoutManager " + layout + " is already attached to a RecyclerView:" + layout.mRecyclerView.exceptionLabel()); } mLayout.setRecyclerView(this); if (mIsAttached) { mLayout.dispatchAttachedToWindow(this); } } mRecycler.updateViewCacheSize(); requestLayout(); }
if (mState.mLayoutStep == State.STEP_START) { dispatchLayoutStep1(); } // set dimensions in 2nd step. Pre-layout should happen with old dimensions for // consistency mLayout.setMeasureSpecs(widthSpec, heightSpec); mState.mIsMeasuring = true; dispatchLayoutStep2();
// now we can get the width and height from the children. mLayout.setMeasuredDimensionFromChildren(widthSpec, heightSpec);
// if RecyclerView has non-exact width and height and if there is at least one child // which also has non-exact width & height, we have to re-measure. if (mLayout.shouldMeasureTwice()) { mLayout.setMeasureSpecs( MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(getMeasuredHeight(), MeasureSpec.EXACTLY)); mState.mIsMeasuring = true; dispatchLayoutStep2(); // now we can get the width and height from the children. mLayout.setMeasuredDimensionFromChildren(widthSpec, heightSpec); } } else {//非自动测量 .... } }
@Override protectedvoidonLayout(boolean changed, int l, int t, int r, int b){ TraceCompat.beginSection(TRACE_ON_LAYOUT_TAG); dispatchLayout(); TraceCompat.endSection(); mFirstLayoutComplete = true; }
voiddispatchLayout(){ 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(); } elseif (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(); }
privatevoiddispatchLayoutStep1(){ .... 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; } //布局前ItemAnimator记录相关信息,recordPreLayoutInformation调用处 final ItemHolderInfo animationInfo = mItemAnimator .recordPreLayoutInformation(mState, holder, ItemAnimator.buildAdapterChangeFlagsForAnimations(holder), holder.getUnmodifiedPayloads()); mViewInfoStore.addToPreLayout(holder, animationInfo); .... } } } 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(); ....
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)) { .... //recordPreLayoutInformation调用处 final ItemHolderInfo animationInfo = mItemAnimator.recordPreLayoutInformation( mState, viewHolder, flags, viewHolder.getUnmodifiedPayloads()); .... } } } // 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; }
privatevoiddispatchLayoutStep3(){ mState.assertLayoutStep(State.STEP_ANIMATIONS); startInterceptRequestLayout(); onEnterLayoutOrScroll(); 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. finalboolean oldDisappearing = mViewInfoStore.isDisappearing( oldChangeViewHolder); finalboolean newDisappearing = mViewInfoStore.isDisappearing(holder); if (oldDisappearing && oldChangeViewHolder == holder) { // run disappear animation instead of change mViewInfoStore.addToPostLayout(holder, animationInfo); } else { final ItemHolderInfo preInfo = mViewInfoStore.popFromPreLayout( oldChangeViewHolder); // we add and remove so that any post info is merged. mViewInfoStore.addToPostLayout(holder, animationInfo); ItemHolderInfo postInfo = mViewInfoStore.popFromPostLayout(holder); if (preInfo == null) { handleMissingPreInfoForChangeError(key, holder, oldChangeViewHolder); } else { animateChange(oldChangeViewHolder, holder, preInfo, postInfo, oldDisappearing, newDisappearing); } } } else { mViewInfoStore.addToPostLayout(holder, animationInfo); } }
// Step 4: Process view info lists and trigger animations mViewInfoStore.process(mViewInfoProcessCallback); }
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(); }
@Override publicvoidprocessPersistent(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(); } } elseif (mItemAnimator.animatePersistence(viewHolder, preInfo, postInfo)) { postAnimationRunner(); } } @Override publicvoidunused(ViewHolder viewHolder){ mLayout.removeAndRecycleView(viewHolder.itemView, mRecycler); } };
而mViewInfoProcessCallback的监听添加是在step3中的step4进行的。
ItemDecoration
为 RecyclerView 添加分割线
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
publicvoidaddItemDecoration(@NonNull ItemDecoration decor, int index){ if (mLayout != null) { mLayout.assertNotInLayoutOrScroll("Cannot add item decoration during a scroll or" + " layout"); } if (mItemDecorations.isEmpty()) { setWillNotDraw(false); } if (index < 0) { mItemDecorations.add(decor); } else { //指定索引添加分割线 mItemDecorations.add(index, decor); } markItemDecorInsetsDirty(); //重新测量、布局、绘制 requestLayout(); }
intfill(RecyclerView.Recycler recycler, LayoutState layoutState, RecyclerView.State state, boolean stopOnFocusable){ // max offset we should set is mFastScroll + available finalint start = layoutState.mAvailable; .... int remainingSpace = layoutState.mAvailable + layoutState.mExtraFillSpace; LayoutChunkResult layoutChunkResult = mLayoutChunkResult; while ((layoutState.mInfinite || remainingSpace > 0) && layoutState.hasMore(state)) { layoutChunkResult.resetInternal(); ... layoutChunk(recycler, state, layoutState, layoutChunkResult); ... if (layoutChunkResult.mFinished) { break; } layoutState.mOffset += layoutChunkResult.mConsumed * layoutState.mLayoutDirection; /** * Consume the available space if: * * layoutChunk did not request to be ignored * * OR we are laying out scrap children * * OR we are not doing pre-layout */ 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; }
voidlayoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state, LayoutState layoutState, LayoutChunkResult result){ View view = layoutState.next(recycler); if (view == null) { // if we are laying out views in scrap, this may return null which means there is // no more items to layout. result.mFinished = true; return; } RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) view.getLayoutParams(); if (layoutState.mScrapList == null) { if (mShouldReverseLayout == (layoutState.mLayoutDirection == LayoutState.LAYOUT_START)) { addView(view); } else { addView(view, 0); } } else { if (mShouldReverseLayout == (layoutState.mLayoutDirection == LayoutState.LAYOUT_START)) { addDisappearingView(view); } else { addDisappearingView(view, 0); } } measureChildWithMargins(view, 0, 0); result.mConsumed = mOrientationHelper.getDecoratedMeasurement(view); int left, top, right, bottom; if (mOrientation == VERTICAL) { if (isLayoutRTL()) { right = getWidth() - getPaddingRight(); left = right - mOrientationHelper.getDecoratedMeasurementInOther(view); } else { left = getPaddingLeft(); right = left + mOrientationHelper.getDecoratedMeasurementInOther(view); } if (layoutState.mLayoutDirection == LayoutState.LAYOUT_START) { bottom = layoutState.mOffset; top = layoutState.mOffset - result.mConsumed; } else { top = layoutState.mOffset; bottom = layoutState.mOffset + result.mConsumed; } } else { top = getPaddingTop(); bottom = top + mOrientationHelper.getDecoratedMeasurementInOther(view);
if (layoutState.mLayoutDirection == LayoutState.LAYOUT_START) { right = layoutState.mOffset; left = layoutState.mOffset - result.mConsumed; } else { left = layoutState.mOffset; right = layoutState.mOffset + result.mConsumed; } } // We calculate everything with View's bounding box (which includes decor and margins) // To calculate correct layout position, we subtract margins. layoutDecoratedWithMargins(view, left, top, right, bottom); // Consume the available space if the view is not removed OR changed if (params.isItemRemoved() || params.isItemChanged()) { result.mIgnoreConsumed = true; } result.mFocusable = view.hasFocusable(); }
通过 next 方法取出来 view ,并且通过 addView 添加到 RecyclerView 里面去
ViewHolder tryGetViewHolderForPositionByDeadline(int position, boolean dryRun, long deadlineNs){ ... boolean fromScrapOrHiddenOrCache = false; ViewHolder holder = null; // 0) If there is a changed scrap, try to find from there if (mState.isPreLayout()) { holder = getChangedScrapViewForPosition(position); fromScrapOrHiddenOrCache = holder != null; } // 1) Find by position from scrap/hidden list/cache if (holder == null) { holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun); if (holder != null) { 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(); } elseif (holder.wasReturnedFromScrap()) { holder.clearReturnedFromScrapFlag(); } recycleViewHolderInternal(holder); } holder = null; } else { fromScrapOrHiddenOrCache = true; } } } if (holder == null) { finalint offsetPosition = mAdapterHelper.findPositionOffset(position); ...
finalint type = mAdapter.getItemViewType(offsetPosition); // 2) Find from scrap/cache via stable ids, if exists if (mAdapter.hasStableIds()) { holder = getScrapOrCachedViewForId(mAdapter.getItemId(offsetPosition), type, dryRun); if (holder != null) { // update position holder.mPosition = offsetPosition; fromScrapOrHiddenOrCache = true; } } 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) { // fallback to pool 从 RecycledViewPool 中取根据 type 取 ViewHolder holder = getRecycledViewPool().getRecycledView(type); if (holder != null) { holder.resetInternal(); if (FORCE_INVALIDATE_DISPLAY_LIST) { invalidateDisplayListInt(holder); } } } if (holder == null) { long start = getNanoTime(); if (deadlineNs != FOREVER_NS && !mRecyclerPool.willCreateInTime(type, start, deadlineNs)) { // abort - we have a deadline we can't meet returnnull; } 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);
} } }
// 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; } elseif (!holder.isBound() || holder.needsUpdate() || holder.isInvalid()) { finalint offsetPosition = mAdapterHelper.findPositionOffset(position); bound = tryBindViewHolderByDeadline(holder, offsetPosition, position, deadlineNs); }
staticclassScrapData{ final ArrayList<ViewHolder> mScrapHeap = new ArrayList<>(); int mMaxScrap = DEFAULT_MAX_SCRAP; long mCreateRunningAverageNs = 0; long mBindRunningAverageNs = 0; } SparseArray<ScrapData> mScrap = new SparseArray<>(); }
java.lang.IllegalArgumentException: view is not a child, cannot hide android.widget.LinearLayout{5ec2029 V.E...... .......D 0,-1164-1080,-1014} at android.support.v7.widget.ChildHelper.unhide(ChildHelper.java:352) at android.support.v7.widget.RecyclerView$Recycler.getScrapOrHiddenOrCachedHolderForPosition(RecyclerView.java:5972) at android.support.v7.widget.RecyclerView$Recycler.tryGetViewHolderForPositionByDeadline(RecyclerView.java:5485) at android.support.v7.widget.RecyclerView$Recycler.getViewForPosition(RecyclerView.java:5448) at android.support.v7.widget.RecyclerView$Recycler.getViewForPosition(RecyclerView.java:5444) at android.support.v7.widget.LinearLayoutManager$LayoutState.next(LinearLayoutManager.java:2224) at android.support.v7.widget.LinearLayoutManager.layoutChunk(LinearLayoutManager.java:1551) at android.support.v7.widget.LinearLayoutManager.fill(LinearLayoutManager.java:1511) at android.support.v7.widget.LinearLayoutManager.scrollBy(LinearLayoutManager.java:1325) at android.support.v7.widget.LinearLayoutManager.scrollVerticallyBy(LinearLayoutManager.java:1061) at android.support.v7.widget.RecyclerView$ViewFlinger.run(RecyclerView.java:4734) at android.view.Choreographer$CallbackRecord.run(Choreographer.java:915) at android.view.Choreographer.doCallbacks(Choreographer.java:727) at android.view.Choreographer.doFrame(Choreographer.java:659) at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:901) at android.os.Handler.handleCallback(Handler.java:790) at android.os.Handler.dispatchMessage(Handler.java:99) at android.os.Looper.loop(Looper.java:197) at android.app.ActivityThread.main(ActivityThread.java:7022) at java.lang.reflect.Method.invoke(Native Method) at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:515) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:837)