其中,AdapterHelper主要用来处理Adapter的操作,入队,更新等并且通过callback回调回RecyclerView来。它还定义了一个内部类UpdateOp,用来封装数据的增删改和移动的操作,并且维护了一个UpdateOp的数组。 ChildHelper则主要帮助RecyclerView来管理子view,它内部维护了一个 List mHiddenViews 数组。
publicvoidsetLayoutManager(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) { ... } 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(); }
voidstopInterceptRequestLayout(boolean performLayoutChildren){ if (mInterceptRequestLayoutDepth < 1) { //noinspection PointlessBooleanExpression if (DEBUG) { thrownew IllegalStateException("stopInterceptRequestLayout was called more " + "times than startInterceptRequestLayout." + exceptionLabel()); } mInterceptRequestLayoutDepth = 1; } if (!performLayoutChildren && !mLayoutFrozen) { // Reset the layout request eaten counter. // This is necessary since eatRequest calls can be nested in which case the other // call will override the inner one. // for instance: // eat layout for process adapter updates // eat layout for dispatchLayout // a bunch of req layout calls arrive
mLayoutWasDefered = false; } if (mInterceptRequestLayoutDepth == 1) { // when layout is frozen we should delay dispatchLayout() if (performLayoutChildren && mLayoutWasDefered && !mLayoutFrozen && mLayout != null && mAdapter != null) { dispatchLayout(); } if (!mLayoutFrozen) { mLayoutWasDefered = false; } } mInterceptRequestLayoutDepth--; }
/** * True if a call to requestLayout was intercepted and prevented from executing like normal and * we plan on continuing with normal execution later. * True表示一个requestLayout调用被拦截了,后面会在合适的时候执行 requestLayout */ boolean mLayoutWasDefered;
// setAdapter的时候会调用 setLayoutFrozen(false),其他地方暂时没看到。 publicvoidsetLayoutFrozen(boolean frozen){ if (frozen != mLayoutFrozen) { assertNotInLayoutOrScroll("Do not setLayoutFrozen in layout or scroll"); if (!frozen) { mLayoutFrozen = false; if (mLayoutWasDefered && mLayout != null && mAdapter != null) { requestLayout(); } mLayoutWasDefered = false; } else { finallong now = SystemClock.uptimeMillis(); MotionEvent cancelEvent = MotionEvent.obtain(now, now, MotionEvent.ACTION_CANCEL, 0.0f, 0.0f, 0); onTouchEvent(cancelEvent); mLayoutFrozen = true; mIgnoreMotionEventTillDown = true; stopScroll(); } } }
protectedvoidonMeasure(int widthSpec, int heightSpec){ if (mLayout == null) { defaultOnMeasure(widthSpec, heightSpec); return; } if (mLayout.isAutoMeasureEnabled()) { finalint widthMode = MeasureSpec.getMode(widthSpec); finalint heightMode = MeasureSpec.getMode(heightSpec);
/** * This specific call should be considered deprecated and replaced with * {@link #defaultOnMeasure(int, int)}. It can't actually be replaced as it could * break existing third party code but all documentation directs developers to not * override {@link LayoutManager#onMeasure(int, int)} when * {@link LayoutManager#isAutoMeasureEnabled()} returns true. */ mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);
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 { ... }
voiddefaultOnMeasure(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. finalint width = LayoutManager.chooseSize(widthSpec, getPaddingLeft() + getPaddingRight(), ViewCompat.getMinimumWidth(this)); finalint height = LayoutManager.chooseSize(heightSpec, getPaddingTop() + getPaddingBottom(), ViewCompat.getMinimumHeight(this));
setMeasuredDimension(width, height); }
publicstaticintchooseSize(int spec, int desired, int min){ finalint mode = View.MeasureSpec.getMode(spec); finalint 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); } } }
/** * The first step of a layout where we; * - process adapter updates * - decide which animation should run * - save information about current views * - If necessary, run predictive layout and save its information */ privatevoiddispatchLayoutStep1(){ mState.assertLayoutStep(State.STEP_START); fillRemainingScrollValues(mState); mState.mIsMeasuring = false; startInterceptRequestLayout(); mViewInfoStore.clear(); onEnterLayoutOrScroll(); processAdapterUpdatesAndSetAnimationFlags(); // step 1 and step 2 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(); finalboolean 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; }
privatevoidprocessAdapterUpdatesAndSetAnimationFlags(){ 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(); }
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(); }
privatevoiddispatchLayoutStep3(){ mState.assertLayoutStep(State.STEP_ANIMATIONS); eatRequestLayout(); 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(); }
finalint count = mItemDecorations.size(); for (int i = 0; i < count; i++) { mItemDecorations.get(i).onDrawOver(c, this, mState); }
...
// 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); } }