RecyclerView,如何做到复用

开篇:

要了解RecyclerView先从它的总体开始看起,防止管中窥豹可见一斑,一叶障目不见泰山。

RecyclerView家族谱

RecyclerView家族谱

上图可以看出RecyclerView的设计是十分灵活的,它把自身的功能拆分成了好几个模块,使开发者可以根据自己的需要进行灵活的配置。

RecyclerView基本用法

我们先从RecyclerView的基本用法入手,开始了解RecyclerView。

// 在Activity的onCreate方法中,这样列表就能正常显示了
recycler_view.adapter = MyAdapter()
// 从此处开始追踪,分析点1
recycler_view.layoutManager = LinearLayoutManager(this,LinearLayoutManager.VERTICAL,false)

源码追踪

从上面我们可以看到RecyclerView的使用十分简单,仅仅根据几行代码就可以完成RecyclerView的配置和使用,那么让我们从setLayoutManager开始追根溯源,看看RecyclerView的奥秘所在。

// 分析点1:RecyclerView#setLayoutManager(@Nullable LayoutManager layout)方法
public void setLayoutManager(@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.
    // 在setLayoutManager方法调用之前如果已经关联了LayoutManager,在此处就会进行旧LayoutManager的清除关联的操作
    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;
    // 判断新设定的LayoutManager是否已经和某RecyclerView进行了关联,如果有则抛出异常
    if (layout != null) {
        if (layout.mRecyclerView != null) {
            throw new IllegalArgumentException("LayoutManager " + layout
                    + " is already attached to a RecyclerView:"
                    + layout.mRecyclerView.exceptionLabel());
        }
        mLayout.setRecyclerView(this);
        if (mIsAttached) {
            mLayout.dispatchAttachedToWindow(this);
        }
    }
    mRecycler.updateViewCacheSize();
    // 可以看到,最终setLayoutManager方法的最终还是调用了requestLayout方法
    // 开启了ViewGroup的onMeasure和onLayout流程,分析点2
    requestLayout();
}

接着只能接着分析RecyclerView的onMeasure和onLayout流程

@Override
protected void onMeasure(int widthSpec, int heightSpec) {
    // 此处defaultOnMeasure方法会尽可能给出一个宽高值,意义就是如果没设置LayoutManager则尽可能给RecyclerView一个兜底的宽高值
    if (mLayout == null) {
        defaultOnMeasure(widthSpec, heightSpec);
        return;
    }
    // 如果设置的LayoutManager是开启了自动测量(一个标志位),如LinearLayoutManager就返回true
    if (mLayout.isAutoMeasureEnabled()) {
        final int widthMode = MeasureSpec.getMode(widthSpec);
        final int 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.
         */
        // 乍一眼看过去以为此处将子View的测量工作交给了LayoutManager去执行
        // 其实追踪分析同样给RecyclerView一个兜底的宽高值,最终调用的还是defaultOnMeasure方法
        mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);

        // 这里表示如果宽高的测量模式都是确切值则不用进行测量了,可直接返回
        final boolean measureSpecModeIsExactly =
                widthMode == MeasureSpec.EXACTLY && heightMode == MeasureSpec.EXACTLY;
        if (measureSpecModeIsExactly || mAdapter == null) {
            return;
        }

        // mState这个实例保存的是RecyclerView滑动状态下的信息(ItemCount,layoutstep等)
        // mState在RecyclerView被创建的同时被创建(初始化)
        // 其中mLayoutStep有三个取值(STEP_START, STEP_LAYOUT, STEP_ANIMATIONS)是控制调用对应的dispatchLayoutStep123系列方法的
        if (mState.mLayoutStep == State.STEP_START) {
            /**
             * 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
             */
             // 解释:在布局开始前收集需要动画的item,以及动画的信息
             // 在该方法末尾会使mState.mLayoutStep = State.STEP_LAYOUT,表示信息收集完毕可以进行布局操作了
            dispatchLayoutStep1();
        }
        // set dimensions in 2nd step. Pre-layout should happen with old dimensions for
        // consistency
        mLayout.setMeasureSpecs(widthSpec, heightSpec);
        mState.mIsMeasuring = true;
        // 测量布局的第二步,有可能调用两次
        // 这个方法最后会使mState.mLayoutStep = State.STEP_ANIMATIONS,表示布局完成可以进行动画操作了,该方法下面会重点介绍
        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.
        // 如果RecyclerView没有确切的宽高,而且至少有一个子View没有确切宽高,则dispatchLayoutStep2就会调用两次来获得RecyclerView的宽高
        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 {
        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
    }
}

上面onMeasure的if分支追踪暂时告一段落,现在看下onLayout方法的调用

@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
    TraceCompat.beginSection(TRACE_ON_LAYOUT_TAG);
    // onLayout这个方法基本没做什么,进而调用了dispatchLayout方法,进一步跟进
    dispatchLayout();
    TraceCompat.endSection();
    mFirstLayoutComplete = true;
}




void dispatchLayout() {
    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;
    // 这个方法中同样是判断当时mState.mLayoutStep的值,确定Layout的状态,从而调用dispatchLayoutStep123方法
    if (mState.mLayoutStep == State.STEP_START) {
        dispatchLayoutStep1();
        mLayout.setExactMeasureSpecsFrom(this);
        // 这里追踪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 {
        // always make sure we sync them (to ensure mode is exact)
        mLayout.setExactMeasureSpecsFrom(this);
    }
    // dispatchLayoutStep3处理动画
    dispatchLayoutStep3();
}

重点开始追踪dispatchLayoutStep2方法,也就是测量子View的开始

/**
 * The second layout step where we do the actual layout of the views for the final state.
 * This step might be run multiple times if necessary (e.g. measure).
 */
private void dispatchLayoutStep2() {
    startInterceptRequestLayout();
    onEnterLayoutOrScroll();
    mState.assertLayoutStep(State.STEP_LAYOUT | State.STEP_ANIMATIONS);
    mAdapterHelper.consumeUpdatesInOnePass();
    mState.mItemCount = mAdapter.getItemCount();
    mState.mDeletedInvisibleItemCountSincePreviousLayout = 0;

    // Step 2: Run layout
    mState.mInPreLayout = false;
    // 终于我们看到这里开始RecyclerView将测量和布局工作委托给LayoutManager
    // 但是在LayoutManager中是空实现,如果自定义LayoutManager必须实现该方法
    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;
    mState.mLayoutStep = State.STEP_ANIMATIONS;
    onExitLayoutOrScroll();
    stopInterceptRequestLayout(false);
}

由于LayoutManager的onLayoutChildren是空实现,所以我们以LinearLayoutManager为例展开追踪分析

// LinearLayoutManager#onLayoutChildren
@Override
public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
    ...
    // 这里Recyclerview这个组件考虑了RecyclerView被回收的状态重新显示
    if (mPendingSavedState != null || mPendingScrollPosition != RecyclerView.NO_POSITION) {
            if (state.getItemCount() == 0) {
                removeAndRecycleAllViews(recycler);
                return;
            }
        }
    if (mPendingSavedState != null && mPendingSavedState.hasValidAnchor()) {
        mPendingScrollPosition = mPendingSavedState.mAnchorPosition;
    }
    // 这个方法创建了LayoutState对象
    // LayoutState对象存储了LayoutManager在当时滑动状态下的关键信息
    // (比如mAvailable屏幕还剩下多少可用空间,mCurrentPosition当前已经摆放到第几个item,mScrollingOffset当前滑动的偏移量等)
    // 当向列表填充item时,就会根据这个对象信息来判断
    ensureLayoutState();
    mLayoutState.mRecycle = false;
    // resolve layout direction
    // 反转布局:比如qq聊天列表,总是显示在最底下聊天的最新消息
    resolveShouldLayoutReverse();
    
    final View focused = getFocusedChild();
    // AnchorInfo布局是锚点View,第一次的时候,mAnchorInfo.mValid为false,会进入if分支
    if (!mAnchorInfo.mValid || mPendingScrollPosition != RecyclerView.NO_POSITION
            || mPendingSavedState != null) {
        mAnchorInfo.reset();
        mAnchorInfo.mLayoutFromEnd = mShouldReverseLayout ^ mStackFromEnd;
        // calculate anchor position and coordinate
        // 收集锚点View的坐标和position信息
        // 锚点View可能是从列表恢复数据中取得当时的锚点
        // 或者是获取了焦点的item的View
        // 不然就是列表的第一个item
        // 锚点View的作用是填充布局时会先从锚点位置从上往下填充,再从锚点位置从下往上填充,这是LinearLayoutManager填充的策略
        updateAnchorInfoForLayout(recycler, state, mAnchorInfo);
        mAnchorInfo.mValid = true;
    } else if (focused != null && (mOrientationHelper.getDecoratedStart(focused)
                    >= mOrientationHelper.getEndAfterPadding()
            || mOrientationHelper.getDecoratedEnd(focused)
            <= mOrientationHelper.getStartAfterPadding())) {
        // This case relates to when the anchor child is the focused view and due to layout
        // shrinking the focused view fell outside the viewport, e.g. when soft keyboard shows
        // up after tapping an EditText which shrinks RV causing the focused view (The tapped
        // EditText which is the anchor child) to get kicked out of the screen. Will update the
        // anchor coordinate in order to make sure that the focused view is laid out. Otherwise,
        // the available space in layoutState will be calculated as negative preventing the
        // focused view from being laid out in fill.
        // Note that we won't update the anchor position between layout passes (refer to
        // TestResizingRelayoutWithAutoMeasure), which happens if we were to call
        // updateAnchorInfoForLayout for an anchor that's not the focused view (e.g. a reference
        // child which can change between layout passes).
        mAnchorInfo.assignFromViewAndKeepVisibleRect(focused, getPosition(focused));
    }
    
    // LLM may decide to layout items for "extra" pixels to account for scrolling target,
    // caching or predictive animations.
    // 在刚开始布局时就调用了LayoutManager#scrollToPosition方法所以下面会计算偏移量
    ...
    
    // 重点,在布局开始前先分门别类的进行ViewHolder的回收
    // 即存储到Recycler的各个集合中,目的是在布局开始时能在Recycler中复用ViewHolder
    // 这里就需要了解Recycler这个类了,可以先跳到下面的Recycler类追踪,追踪完再回退到这里继续分析
    // 为什么在布局前要先回收,是因为notifyDataSetChanged等方法都会调用到onLayoutChildren方法
    // 因为当RecyclerView填充Item的时候都会从Recycler中获取是否有可复用的ViewHolder,所以在复用前要先回收,当复用时就从回收了的Recycler中取。
    detachAndScrapAttachedViews(recycler);
    ...
    // 回收完,开始布局流程
    // 这里是判断是否要倒序布局,一般为false
    if (mAnchorInfo.mLayoutFromEnd) {
        ...
    } else {
        // 这里会先从锚点从上往下,再从锚点从下往上,这两种都是调用fill方法进行填充,重点分析fill方法
        // fill towards end
        updateLayoutStateToFillEnd(mAnchorInfo);
        mLayoutState.mExtraFillSpace = extraForEnd;
        // 重点,追踪下面分析的fill方法流程
        fill(recycler, mLayoutState, state, false);
        endOffset = mLayoutState.mOffset;
        final int lastElement = mLayoutState.mCurrentPosition;
        if (mLayoutState.mAvailable > 0) {
            extraForStart += mLayoutState.mAvailable;
        }
        // fill towards start
        updateLayoutStateToFillStart(mAnchorInfo);
        mLayoutState.mExtraFillSpace = extraForStart;
        mLayoutState.mCurrentPosition += mLayoutState.mItemDirection;
        // 重点,追踪下面分析的fill方法流程
        fill(recycler, mLayoutState, state, false);
        startOffset = mLayoutState.mOffset;
        
        if (mLayoutState.mAvailable > 0) {
            extraForEnd = mLayoutState.mAvailable;
            // start could not consume all it should. add more items towards end
            updateLayoutStateToFillEnd(lastElement, endOffset);
            mLayoutState.mExtraFillSpace = extraForEnd;
            fill(recycler, mLayoutState, state, false);
            endOffset = mLayoutState.mOffset;
        }
    }
    ...
}

Recycler类的介绍,处理四级缓存的类,也是RecyclerView的重点类

public final class Recycler {
    //#1 mAttachedScrap,mChangedScrap同处一级缓存,复用时不需要重新bindViewHolder绑定数据
    ArrayList<ViewHolder> mAttachedScrap;
    ArrayList<ViewHolder> mChangedScrap;
    
    //#2 mCachedViews为二级缓存,随着上下滑动被滑出去的item对应的ViewHolder就会存储在这里
    //   当再次滑进屏幕的时候也是不需要重新bindViewHolder绑定数据的,可通过setItemCacheSize调整,默认大小为2,
    //   也有情况是当一直下滑时,复用了mCachedViews中的ViewHolder,此时则需要重新绑定数据
    ArrayList<ViewHolder> mCachedViews;
    
    //#3 mViewCacheExtension三级缓存,属于扩展,开发者自定义拓展View缓存
    ViewCacheExtension mViewCacheExtension;
    
    //#4 mRecyclerPool为四级缓存,当mCachedViews即二级缓存放不下的情况下才会放到这里,根据viewType存取ViewHolder,
    //   可通过setRecycledViewPool调整,每个类型容量默认为5
    RecycledViewPool mRecyclerPool;

}

fill方法流程分析

int fill(RecyclerView.Recycler recycler, LayoutState layoutState,
            RecyclerView.State state, boolean stopOnFocusable) {
    ...
    int remainingSpace = layoutState.mAvailable + layoutState.mExtraFillSpace;
        LayoutChunkResult layoutChunkResult = mLayoutChunkResult;
    // 开启一个循环,判断当前方向上是否有剩余空间可以填充item
    while ((layoutState.mInfinite || remainingSpace > 0) && layoutState.hasMore(state)) {
        ...
        // 循环里调用layoutChunk方法填充item
        layoutChunk(recycler, state, layoutState, layoutChunkResult);
        ...
    }

    ...
}




// layoutChunk方法
void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state,
        LayoutState layoutState, LayoutChunkResult result) {
    // 从recycler里面获取View,这个方法是复用机制的开始,可以在下面查看复用流程的流程图
    View view = layoutState.next(recycler);
    ...
    if (layoutState.mScrapList == null) {
            if (mShouldReverseLayout == (layoutState.mLayoutDirection
                    == LayoutState.LAYOUT_START)) {
                // 会调用layoutmanager的addView方法,判断该view对应的viewholder是否是即将被删除的View
                // 如果是则加入到即将被删除的集合中,如果这个view之前已经添加到recyclerview中,则无需再次添加,否则就添加到recyclerview上
                addView(view);
            } else {
                addView(view, 0);
            }
    } else {
        if (mShouldReverseLayout == (layoutState.mLayoutDirection
                == LayoutState.LAYOUT_START)) {
            addDisappearingView(view);
        } else {
            addDisappearingView(view, 0);
        }
    }
    // 测量child的上下左右margin
    measureChildWithMargins(view, 0, 0);
    ...
    // We calculate everything with View's bounding box (which includes decor and margins)
    // To calculate correct layout position, we subtract margins.
    // 把子View摆放到合适的位置上,会考虑到ItemDecoration的情况
    layoutDecoratedWithMargins(view, left, top, right, bottom);
    ...
}

Recycler的复用机制

RecyclerView复用机制

总体概括:

  1. layoutmanager向列表填充item时,都会向Recycler索取ViewHolder
  2. 在索取时,Recycler先从一级缓存即mAttachedScrap和mChangedScrap中查找是否有可复用的ViewHolder,它们是屏幕内的ViewHolder,不需要重新绑定数据
  3. 如果在一级缓存找不到,则会在mCacheViews二级缓存中找,如果找到了,还要进行位置的判断,如果位置不变,则表示刚滑出屏幕的被滑回来了,不需要重新绑定数据,否则则比如是向上滑的item需要使用复用的ViewHolder,这种情况需要重新绑定数据
  4. 如果二级缓存找不到,则需要在三级缓存也就是开发这自定义的扩展mViewCacheExt中查找(很少被使用).
  5. 最后都找不到,只能在四级缓存mRecyclerPool里查找,在RecyclerPool里是根据viewType去查找,如果有则返回,没有就会调用Adapter类的onCreateViewHolder去创建ViewHolder并返回

上面是复用机制的总体概括,下面进行代码的分析

// 复用的阶段,最终会调用Recycler#tryGetViewHolderForPositionByDeadline方法
ViewHolder tryGetViewHolderForPositionByDeadline(int position,
        boolean dryRun, long deadlineNs) {
    ...
    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
    // 这里的顺序:
    // 从mAttachedScrap中获取,如果有则返回
    // 没有则调用childHelper#findHiddenNonRemovedView方法,从正在做删除动画的集合中找是否有满足复用的viewHolder
    // 还没找到则从mCachedViews中查找
    if (holder == null) {
        holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun);
        if (holder != null) {
            // 如果获取的ViewHolder不为空
            // 就会判断获取的ViewHolder的viewtype和需要填充的item的viewtype是否一致
            // 判断是否存在hasStableId身份唯一标识,只有都符合才复用
            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;
            }
        }
    }
    if (holder == null) {
        ...
        // 如果holder仍为null,去mViewCacheExtension中查找,一般mViewCacheExtension为null,不会进入这个if
        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());
                }
            }
        }
        if (holder == null) { // fallback to pool
            if (DEBUG) {
                Log.d(TAG, "tryGetViewHolderForPositionByDeadline("
                        + position + ") fetching from shared pool");
            }
            // 最后还找不到才去RecyclerPool中查找
            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
                return null;
            }
            // 实在在缓存中找不到,调用createHolder方法进行ViewHolder的创建
            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");
            }
        }
    }
    ...
}

到这里RecyclerView的测量,布局,复用就分析到一段落了。

小结:

  • RecyclerView是一种插拔式的设计模式
  • 尽量指定RecyclerView和Item的宽和高,否则在onMeasure的过程中,有可能会调用两次dispatchLayoutStep2()进行测量
  • 尽量使用定向刷新notifyItemChanged,而不是notifyDataSetChanged()
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,332评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,508评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,812评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,607评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,728评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,919评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,071评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,802评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,256评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,576评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,712评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,389评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,032评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,798评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,026评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,473评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,606评论 2 350