文章目录

  • 前言
  • RecyclerView.Recycler
    • 主要成员变量
    • RecycledViewPool
      • 成员变量
      • 主要方法
        • getScrapDataForType
        • setMaxRecycledViews
        • getRecycledView
        • putRecycledView
    • ViewCacheExtension
    • 主要方法
      • getViewForPosition
      • recycleView
    • 补充
      • mChangedScrap 和 mAttachedScrap 中的 View 从何而来
        • Recycler#scrapView
  • 场景分析
    • 第一次 layout
      • LinearLayoutManager#onLayoutChildren
      • LinearLayoutManager#fill
      • LinearLayoutManager#layoutChunk
    • 更新列表
      • Adapter#notifyDataSetChanged
        • LayoutManager#detachAndScrapAttachedViews
        • Recycler#recycleViewHolderInternal
      • Adapter#notifyItemChanged
        • RecyclerView#dispatchLayoutStep1
        • LayoutManager#scrapOrRecycleView
  • 后记
  • 参考

前言

RecyclerView 是一个好用又复杂的控件,其功能的高度解耦化,规范化的 ViewHolder 写法,以及对动画的友好支持,都是它与传统 ListView 的区别。

它有几大模块:

  • LayoutManager:控制 item 的布局
  • RecyclerView.Adapter:为 RecyclerView 提供数据
  • ItemDecoration:为 RecyclerView 添加分割线
  • ItemAnimator:控制 item 的动画
  • Recycler:负责回收和提供 View,和 RecyclerView 的复用机制相关

下面就从源码(API 28)角度分析 RecyclerView,RecyclerView 的源码很复杂,很难在一篇文章内讲完,所以打算分几篇来讲,本文是第一篇,将围绕 RecyclerView 的内部类 Recycler 展开分析:

RecyclerView.Recycler

首先看一下它的作用,源码上是这样写的:

A Recycler is responsible for managing scrapped or detached item views for reuse.

意思就是 Recycler 负责管理废弃或被 detached 的 item 视图,以便重复利用。

它有以下几个成员变量:

主要成员变量

final ArrayList<ViewHolder> mAttachedScrap = new ArrayList<>();ArrayList<ViewHolder> mChangedScrap = null;final ArrayList<ViewHolder> mCachedViews = new ArrayList<ViewHolder>();RecycledViewPool mRecyclerPool;private ViewCacheExtension mViewCacheExtension;

这几个成员变量都和 RecyclerView 的缓存相关,如果按照四级缓存的话,它们可以这样划分:

第一级缓存:mAttachedScrap、mChangedScrap

第二级缓存:mCachedViews

第三级缓存:ViewCacheExtension

第四级缓存:RecycledViewPool

后面再介绍 mAttachedScrap、mChangedScrap、mCachedViews 具体存的是哪些 ViewHolder。

现在先了解下 RecycledViewPool 和 ViewCacheExtension这两个类:

RecycledViewPool

继续先看官方注释:

RecycledViewPool lets you share Views between multiple RecyclerViews.

RecycledViewPool 用于在多个 RecyclerView 间共享 View。

在使用时,只需创建 RecycledViewPool 实例,然后调用 RecyclerView 的 setRecycledViewPool(RecycledViewPool) 方法即可。

RecycledViewPool 存储在 Recycler 中,通过 Recycler 存取。

成员变量

RecycledViewPool 有一个重要的成员变量:

    // SparseArray 类似于 key 为 int 类型 的 HashMapSparseArray<ScrapData> mScrap = new SparseArray<>();

其中 ScrapData 的定义如下:

    static class ScrapData {final ArrayList<ViewHolder> mScrapHeap = new ArrayList<>();int mMaxScrap = DEFAULT_MAX_SCRAP;  // 5long mCreateRunningAverageNs = 0;long mBindRunningAverageNs = 0;}

mScrap 是一个 <int, ScrapData> 的映射,其中 int 代表了 viewType,ScrapData 则存储了一个 ViewHolder 集合。

主要方法

getScrapDataForType

    private ScrapData getScrapDataForType(int viewType) {ScrapData scrapData = mScrap.get(viewType);if (scrapData == null) {scrapData = new ScrapData();mScrap.put(viewType, scrapData);}return scrapData;}

该方法根据 viewType 获取相应的 ScrapData,如果该 viewType 还没有绑定 ScrapData,就新创建一个 ScrapData 并绑定到该 viewType。

setMaxRecycledViews

    public void setMaxRecycledViews(int viewType, int max) {ScrapData scrapData = getScrapDataForType(viewType);scrapData.mMaxScrap = max;final ArrayList<ViewHolder> scrapHeap = scrapData.mScrapHeap;// 从后面开始删除,直到满足新的容量while (scrapHeap.size() > max) {scrapHeap.remove(scrapHeap.size() - 1);}}

该方法可以设置相应 viewType 的 View 容量,超出容量时,从后面开始删除,直到满足新的容量。

getRecycledView

    public ViewHolder getRecycledView(int viewType) {final ScrapData scrapData = mScrap.get(viewType);if (scrapData != null && !scrapData.mScrapHeap.isEmpty()) {final ArrayList<ViewHolder> scrapHeap = scrapData.mScrapHeap;return scrapHeap.remove(scrapHeap.size() - 1);}return null;}

该方法根据 viewType 获取一个 ViewHolder,获取到的 ViewHolder 将会被移除出 Scrap 堆。获取不到则返回 null。

putRecycledView

    public void putRecycledView(ViewHolder scrap) {final int viewType = scrap.getItemViewType();final ArrayList<ViewHolder> scrapHeap = getScrapDataForType(viewType).mScrapHeap;// 容量已满,不再添加if (mScrap.get(viewType).mMaxScrap <= scrapHeap.size()) {return;}// 重置 ViewHolder,例如清空 flagsscrap.resetInternal();// 将该 ViewHolder 添加到对应 viewType 的 集合中缓存起来scrapHeap.add(scrap);}

该方法也很好理解,根据 ViewHolder 的 viewType 放入 RecycledViewPool 的相应集合中,如果集合已满,不再添加。

接下来看另一个类:

ViewCacheExtension

ViewCacheExtension 是一个由开发者控制的 View 缓存帮助类,其定义如下:

    public abstract static class ViewCacheExtension {/*** Returns a View that can be binded to the given Adapter position.*/@Nullablepublic abstract View getViewForPositionAndType(@NonNull Recycler recycler, int position,int type);}

开发者可以实现这个抽象类,通过调用 RecyclerView 的 setViewCacheExtension(ViewCacheExtension) 方法设置,最终将 ViewCacheExtension 存储在 Recycler 中。

当调用 Recycler 的 getViewForPosition 方法时,如果 attached scrap 和 已经缓存都没有找到合适的 View,就会调用 ViewCacheExtension 的 getViewForPositionAndType 方法来获取 View。

需要注意的是,Recycler 不会对这个类做任何缓存处理,是否需要缓存 View 由开发者自己控制。

主要方法

看完这两个类,现在回到 Recycler 中,看一下 Rcycler 的主要方法:

getViewForPosition

getViewForPosition 方法比较重要,用于获取某个位置需要展示的 View,如下:

    public View getViewForPosition(int position) {return getViewForPosition(position, false);}View getViewForPosition(int position, boolean dryRun) {return tryGetViewHolderForPositionByDeadline(position, dryRun, FOREVER_NS).itemView;}

继续看 tryGetViewHolderForPositionByDeadline 方法,该方法会依次从几个缓存中获取,分别来看一下:

    // 如果是处于预布局阶段(先简单理解为执行 dispatchLayoutStep1 方法)// (其实下面方法要返回 ture 还需要开启“预处理动画”,这跟动画有关,先不多说)if (mState.isPreLayout()) {holder = getChangedScrapViewForPosition(position);fromScrapOrHiddenOrCache = holder != null;}

第一步,从 mChangedScrap 中获取,获取不到就返回 null。

如果 holder 还是为 null,执行下面代码:

 // 1) Find by position from scrap/hidden list/cacheif (holder == null) {holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun);if (holder != null) {if (!validateViewHolderForOffsetPosition(holder)) {// recycle holder (and unscrap if relevant) since it can't be used// 回收无效的 ViewHolder// ...} else {fromScrapOrHiddenOrCache = true;}}}

第二步,根据 position 依次从 mAttachedScrap、mHiddenViews(存储在 ChildHelper 类)、mCachedViews 中获取缓存的 ViewHolder。

可以从 mHiddenViews 获取到缓存的话,就将其从 mHiddenViews 移除并添加到 Scrap 缓存(根据情况添加到 mAttachedScrap 或 mChangedScrap)。可以从 mCacheViews 中获取到缓存的话,就将其从 mCacheViews 移除。

获取到后,发现无效的话,将对获取到的 ViewHolder 进行清理并回收(放入 mCachedViews 或 RecycledViewPool)。

获取不到,就继续往下执行:

    // 默认返回 false,可通过 Adapter.setHasStableIds 方法设置该值if (mAdapter.hasStableIds()) {// 根据 id 依次在 mAttachedScrap、mCachedViews 中获取缓存holder = getScrapOrCachedViewForId(mAdapter.getItemId(offsetPosition),type, dryRun);if (holder != null) {holder.mPosition = offsetPosition;fromScrapOrHiddenOrCache = true;}}

第三步,根据 id 依次从 mAttachedScrap、mCachedViews 中获取缓存,还没有获取到就继续往下:

 // 如果用户设置了 ViewCacheExtensionif (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);// ...}}

第四步,从用户设置的 ViewCacheExtension 中获取缓存,没有获取到就继续往下:

 if (holder == null) { // fallback to poolholder = getRecycledViewPool().getRecycledView(type);// ...}

第五步,根据 viewType 从 RecycledViewPool 中得到缓存。

RecycledViewPool 已经是最后一级缓存了,如果这里也没有获取到,只能通过 Adapter 的 createViewHolder 方法创建一个 ViewHolder:

 if (holder == null) {holder = mAdapter.createViewHolder(RecyclerView.this, type);// ...}

最后小结一下获取某个位置的 View 的过程:

  1. 先后根据 position 或 id 从 mChangedScrap 中获取缓存
  2. 根据 position 依次从 mAttachedScrap、mHiddenViews(存储在 ChildHelper 类)、mCachedViews 中获取缓存
  3. 根据 id 依次从 mAttachedScrap、mCachedViews 中获取缓存
  4. 从用户设置的 ViewCacheExtension 中获取缓存
  5. 从 RecycledViewPool 中得到缓存的废弃 ViewHolder
  6. 通过 Adapter 的 createViewHolder 方法创建一个 ViewHolder

recycleView

既然叫 Recycler,那肯定要做回收工作了,recycleView 方法就完成了这些工作,下面看一下该方法的实现:

    public void recycleView(@NonNull View view) {ViewHolder holder = getChildViewHolderInt(view);// ...recycleViewHolderInternal(holder);}

继续看 recycleViewHolderInternal:

    void recycleViewHolderInternal(ViewHolder holder) {// ...if (forceRecycle || holder.isRecyclable()) {if (mViewCacheMax > 0&& !holder.hasAnyOfTheFlags(ViewHolder.FLAG_INVALID| ViewHolder.FLAG_REMOVED| ViewHolder.FLAG_UPDATE| ViewHolder.FLAG_ADAPTER_POSITION_UNKNOWN)) {int cachedViewSize = mCachedViews.size();// 若 CacheViews 达到最大容量(2),将最老的缓存从 CacheViews 移除,并添加到 RecycledViewPool 中if (cachedViewSize >= mViewCacheMax && cachedViewSize > 0) {recycleCachedViewAt(0);cachedViewSize--;}// ...// 将 View 缓存到 mCachedViews 中mCachedViews.add(targetCacheIndex, holder);cached = true;}if (!cached) {// 没有添加到 mCachedViews 的话,就添加到 RecycledViewPool 中addViewHolderToRecycledViewPool(holder, true);recycled = true;}}// ...}

可以看到,回收过程主要涉及到两层缓存,第一层缓存是 CacheViews,在添加时,如果发现原来的 CacheViews 已经达到最大容量,就将最老的缓存从 CacheViews 移除,并添加到 RecycledViewPool。第二层缓存是 RecycledViewPool,如果不能添加到 mCacheViews,就会添加到 RecycledViewPool 中。

补充

mChangedScrap 和 mAttachedScrap 中的 View 从何而来

从前面可以得知,在执行 Recycler 的 recycleView 方法时,会将回收的 View 缓存到 mCahceViews 或 recycledViewPool 中,那么另外两个 Scrap 缓存(mChangedScrap 和 mAttachedScrap)中的 View 是何时添加进来的呢?

无论是 mAttachedScrap 还是 mChangedScrap ,它们获得 View 的途径都只有一个,那就是通过 Recycler 的 scrapView 方法。先看下该方法:

Recycler#scrapView

    void scrapView(View view) {final ViewHolder holder = getChildViewHolderInt(view);// 满足这几个条件中的一个就可以进入 if 循环,有机会将 View 缓存到 mAttachedScrap// 1. ViewHolder 设置了 FLAG_REMOVED 或 FLAG_INVALID// 2. ViewHolder 没有设置 FLAG_UPDATE// 3. 没有设置动画或者动画可以重用该 ViewHolderif (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());}// 给 ViewHolder 绑定 Recyclerholder.setScrapContainer(this, false);mAttachedScrap.add(holder);} // 不满足上述任意一个条件时,将 View 缓存到 mChangedScrap 中else {if (mChangedScrap == null) {mChangedScrap = new ArrayList<ViewHolder>();}holder.setScrapContainer(this, true);mChangedScrap.add(holder);}}

该方法通过判断 ViewHolder 的 flag 以及是否设置 ItemAnimator 等,决定将 View 缓存到 mAttachedScrap 还是 mChangedScrap。

那么该方法在何时调用呢?有两种情况:

  1. 以 LinearLayoutManager 为例,在它的 onLayoutChildren 方法中,会调用
    detachAndScrapAttachedViews(recycler);

该方法定义在 RecyclerView 的 LayoutManager 中,它继续调用 scrapOrRecycleView 方法,如果在该方法符合条件就调用 Recycler 的 scrapView 方法。

  1. 通过 mHiddenViews 获取到缓存时,也会调用 scrapView 方法。

场景分析

下面就根据一些场景来分析下 Recycler 是如何进行回收和复用的。

第一次 layout

由于这里不是专门分析 layout 过程的,就不从 onLayout 开始说了,中间的过程省略掉,它最终会调用到 LayoutManager 的 onLayoutChildren,这里以 LinearLayoutManager 为例:

LinearLayoutManager#onLayoutChildren

    @Overridepublic 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

    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);}// ...}}

主要看(1)处的 layoutChunk 方法,只要还有需要填充的空间,就会不断调用该方法:

LinearLayoutManager#layoutChunk

    void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state,LayoutState layoutState, LayoutChunkResult result) {// (1)View view = layoutState.next(recycler);// ...// 默认情况下,layoutState.mScrapList 等于 nullif (layoutState.mScrapList == null) {if (mShouldReverseLayout == (layoutState.mLayoutDirection== LayoutState.LAYOUT_START)) {// (2)addView(view);} else {addView(view, 0);}} else {// ...}}

(2)处的 addView 方法就不多说了,该方法将得到的子 View 添加到 RecyclerView 中。主要看(1)处,看看子 View 从何而来:

    View next(RecyclerView.Recycler recycler) {// ...final View view = recycler.getViewForPosition(mCurrentPosition);return view;}

这个方法是不是很熟悉呢?没错,它就是之前分析的 Recycler 的 getViewForPosition 方法。

不过由于现在没有任何缓存,所以第一次 layout 的时候是通过 Adapter 的 createViewHolder 来创建子 View的,并且没有添加任何缓存。

更新列表

更新列表可以使用 Adapter 的一系列 notify 方法,这里分析其中两个方法:notifyDataSetChanged 和 notifyItemChanged(int)。

Adapter#notifyDataSetChanged

该方法最终调用了 RecyclerViewDataObserver 的 onChanged 方法:

    @Overridepublic void onChanged() {// ...// 该方法主要做了这两件事// 1. 给所有 ViewHolder 添加了 FLAG_UPDATE 和 FLAG_INVALID// 2. 默认情况下(mHasStableIds 为 false)清空 CacheViewsprocessDataSetCompletelyChanged(true);if (!mAdapterHelper.hasPendingUpdates()) {// 进行视图重绘requestLayout();}}

该方法会进行视图重绘,又来到了 layout 过程,继续以 LinearLayoutManager 为例,从它的 onLayoutChildren 方法看起,由于分析第一次 layout 时已经看过一遍了,这次主要看下不同之处:

    @Overridepublic void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {// ...detachAndScrapAttachedViews(recycler);// ...}

主要区别在于 detachAndScrapAttachedViews 方法,这次它开始起作用了,该方法在 RecyclerView 的 LayoutManager 中定义,看下它的实现:

LayoutManager#detachAndScrapAttachedViews

    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 方法:

    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

        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 方法缓存该 ViewHolderaddViewHolderToRecycledViewPool(holder, true);recycled = true;}} // ...}

最终被移除的子 View 缓存到了 RecycledViewPool 中。

后面在调用 fill 方法进行布局填充时,就可以从 RecycledViewPool 中拿取缓存的 View。

Adapter#notifyItemChanged

该方法传入一个 int 参数,表示要数据有更新的 item 的 position。

    public final void notifyItemChanged(int position) {mObservable.notifyItemRangeChanged(position, 1);}

最终调用 RecyclerViewDataObserver 的 onItemRangeChanged 方法:

    @Overridepublic void onItemRangeChanged(int positionStart, int itemCount, Object payload) {// 会在 mAdapterHelper 中创建一个 UpdateOp,将信息保存起来if (mAdapterHelper.onItemRangeChanged(positionStart, itemCount, payload)) {// 如果可以进行更新操作,执行该方法triggerUpdateProcessor();}}

继续看 triggerUpdateProcessor 方法:

    void triggerUpdateProcessor() {// 判断条件默认为 false,执行 else 块if (POST_UPDATES_ON_ANIMATION && mHasFixedSize && mIsAttached) {// ...} else {mAdapterUpdateDuringMeasure = true;requestLayout();}}

在保存了一些信息后,还是进行视图重绘。来到了 layout 过程后,还是以 LinearLayoutManager 为例,这次先看下布局过程的 step1,也就是 dispatchLayoutStep1 方法:

RecyclerView#dispatchLayoutStep1

    private void dispatchLayoutStep1() {// ...processAdapterUpdatesAndSetAnimationFlags();// ...}

主要看 processAdapterUpdatesAndSetAnimationFlags 方法,从名字也可以看出,它负责更新 adapter 的信息:

    private void processAdapterUpdatesAndSetAnimationFlags() {// ...if (predictiveItemAnimationsEnabled()) {mAdapterHelper.preProcess();} else {mAdapterHelper.consumeUpdatesInOnePass();}// ...}

这里借助了 mAdapterHelper,它最终又通过接口回调(回调了 markViewHoldersUpdated 方法)调用了 RecyclerView 的 viewRangeUpdate 方法:

    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

    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 方法。该方法在前面也分析过了,这里再看一次:

    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。

其实还有一个常见的场景是滑动操作,滑动出屏幕的子 View 将会缓存到 mCachedView,不过这里就不详细说了,在之后会在其他文章专门分析滑动这块。

后记

本文围绕 Recycler 展开叙述,重点是要通过它的几个成员变量了解它的缓存机制,四级缓存分别是什么,是在何时调用的,各自起到的作用,不同场景下使用哪种缓存等。

Recycler 和 LayoutManager 的布局以及动画都有联系,例如 LayoutManager 负责布局,它决定获取子 View 和回收子 View 的时机,具体的工作就交由 Recycler 负责。这些会在之后对 RecyclerView 的其他方面作分析时进行更详细的说明。

参考

  • RecyclerView 源码分析
  • RecyclerView源码分析
  • RecyclerView的复用机制
  • RecyclerView的缓存分析
  • RecyclerView 源码分析(三) - RecyclerView的缓存机制

RecyclerView 源码分析(一):Recycler相关推荐

  1. RecyclerView 源码分析

    RecyclerView 源码分析 写在前面: RecyclerView 是一个越用越优雅的控件,相信大家对于 RecyclerView 的使用也已经比较熟悉了.其功能的高度解耦化,规范 ViewHo ...

  2. 【Netty】对象重用的秘密:Recycler源码分析

    Netty作为一个高性能的网络IO框架,在代码层面做了大量的优化,为了减轻GC的压力,尽可能的使对象可以被重用,避免频繁的创建和销毁. ​ Recycler抽象类是Netty实现的,基于线程本地变量S ...

  3. Fresco源码分析之Hierarchy

    上篇文章我们分析了Fresco中的DraweeView,对其中的一些原理以及方法进行了解析.在这过程中我们了解到,DraweeView中是通过DraweeHolder来统一管理的.而DraweeHol ...

  4. 【Android 插件化】VirtualApp 源码分析 ( 启动应用源码分析 | HomePresenterImpl 启动应用方法 | VirtualCore 启动插件应用最终方法 )

    文章目录 一.启动应用源码分析 1.HomeActivity 启动应用点击方法 2.HomePresenterImpl 启动应用方法 3.VirtualCore 启动插件应用最终方法 一.启动应用源码 ...

  5. 【Android 插件化】VirtualApp 源码分析 ( 添加应用源码分析 | LaunchpadAdapter 适配器 | 适配器添加元素 | PackageAppData 元素 )

    文章目录 一.添加应用源码分析 1.LaunchpadAdapter 适配器 2.适配器添加元素 3.PackageAppData 元素 一.添加应用源码分析 1.LaunchpadAdapter 适 ...

  6. 【Android 事件分发】ItemTouchHelper 源码分析 ( OnItemTouchListener 事件监听器源码分析 二 )

    Android 事件分发 系列文章目录 [Android 事件分发]事件分发源码分析 ( 驱动层通过中断传递事件 | WindowManagerService 向 View 层传递事件 ) [Andr ...

  7. 【Android 事件分发】ItemTouchHelper 源码分析 ( OnItemTouchListener 事件监听器源码分析 )

    Android 事件分发 系列文章目录 [Android 事件分发]事件分发源码分析 ( 驱动层通过中断传递事件 | WindowManagerService 向 View 层传递事件 ) [Andr ...

  8. DialogFragment源码分析

    2019独角兽企业重金招聘Python工程师标准>>> 目录介绍 1.最简单的使用方法 1.1 官方建议 1.2 最简单的使用方法 1.3 DialogFragment做屏幕适配 2 ...

  9. Dialog源码分析

    目录介绍 1.简单用法 2.AlertDialog源码分析 2.1 AlertDialog.Builder的构造方法 2.2 通过AlertDialog.Builder对象设置属性 2.3 build ...

最新文章

  1. 在实际案例(费用系统)中落地DDD
  2. java算法题常用到的一些api,含面试题+答案
  3. 为什么叫python编程-运维为什么要学编程?编程为什么是Python?
  4. node.js require 自动执行脚本 并生成html,利用node.js实现自动生成前端项目组件的方法详解...
  5. 设计模式のObserver Pattern(观察者模式)----行为模式
  6. matlab 自适应噪声对消,基于Matlab的RLS自适应语音噪声对消系统的设计与实现
  7. 用Webbench进行网站压力测试
  8. 如何在Cocos2D游戏中实现A*寻路算法(三)
  9. Apache下域名虚拟主机在个人主页目录上实现过程
  10. 大数据工程师成长之路
  11. 分解质因数FZU - 1075
  12. JS中三个点(...)是什么鬼?
  13. 计算机三维设计论文摘要,三维动画论文摘要
  14. 王占祥:公募基金券商交易模式
  15. 导入EXCEL2007报错外部表不是预期的格式错误
  16. 【区块链实战】什么是DAPP,区块链开发如何进行技术选型
  17. 最新Elasticsearch8.4.3 + Kibana8.4.3在云服务器Centos7.9安装部署(参考官方文档)
  18. 财路网每日原创推送:2019年区块链在企业应用中扮演的角色
  19. 微信小程序导入云开发数据库表报错问题解决
  20. C++-生日-星座-性格查询

热门文章

  1. 华为机试:最长广播效应
  2. c#垂直投影法_C#教程之c#求点到直线的投影点坐标
  3. mysql执行脚本文件时总是显示failed to open file
  4. PAT甲级1050 String Subtraction
  5. nvidia-docker2完成tensorflow/serving深度学习模型在线部署
  6. 购买二手房提取住房公积金
  7. Python英文文本分词(无空格)模块wordninja的使用实例
  8. 移动办公软件怎么用?移动办公软件下载教程
  9. Unity 3D游戏——神鬼传说
  10. Pytorch数据读取加速方法