RecyclerView源码剖析: 数据刷新

本片文章来分析RecyclerView数据刷新,你将会看到缓存在数据刷新过程当中的使用,以及不一样的数据刷新方法对性能的影响。java

阅读本文前,你须要前面的两篇文章的基础数组

  1. RecyclerView源码剖析: 基本显示
  2. RecyclerView源码剖析: 滑动,子View回收再利用

设置数据观察者

RecyclerView设置Adapter的时候,会给Adapter设置一个数据观察者RecyclerViewDataObserver mObserver。在Adapter通知数据更新的时候,这个观察者会根据状况完成界面刷新工做。缓存

全局刷新

首先来分析最简单粗暴,并且也是最经常使用的数据刷新方法,Adapter#notifyDataSetChanged(),它表示数据彻底改变,界面须要全局刷新。app

Adapter#notifyDataSetChanged()会调用RecyclerViewDataObserver#onChanged()方法ide

private class RecyclerViewDataObserver extends AdapterDataObserver {
    @Override
    public void onChanged() {
        // 表示数据的结构已经彻底改变
        mState.mStructureChanged = true;
        // 1.预处理工做,响应数据集改变
        processDataSetCompletelyChanged(true);
        // 2.若是没有等待更新的操做,那么就当即请求从新布局
        if (!mAdapterHelper.hasPendingUpdates()) {
            requestLayout();
        }
    }
}
复制代码

RecyclerViewDataObserver接收到数据彻底改变的消息后,它首先作一些预处理工做,以响应数据集改变,而后请求从新布局来刷新界面。工具

预处理

首先来看看数据集改变的预处理工做到底作了啥布局

void processDataSetCompletelyChanged(boolean dispatchItemsChanged) {
    // 参数传入的值为true,代表须要分发数据改变的事件
    mDispatchItemsChangedEvent |= dispatchItemsChanged;
    // 代表在layout后,数据集彻底改变了
    mDataSetHasChangedAfterLayout = true;
    // 标记已知的View为无效的
    markKnownViewsInvalid();
}
复制代码

processDataSetCompletelyChanged()首先作了一些状态标记,而后调用markKnownViewsInvalid()来标记已知的View为无效的。post

void markKnownViewsInvalid() {
    final int childCount = mChildHelper.getUnfilteredChildCount();
    for (int i = 0; i < childCount; i++) {
        final ViewHolder holder = getChildViewHolderInt(mChildHelper.getUnfilteredChildAt(i));
        if (holder != null && !holder.shouldIgnore()) {
            // 1. 遍历RecyclerView全部子View,设置FLAG_UPDATE和FLAG_INVALID标签
            holder.addFlags(ViewHolder.FLAG_UPDATE | ViewHolder.FLAG_INVALID);
        }
    }
    // 2. 标记ItemDecoration区域为dirty
    markItemDecorInsetsDirty();
    // 3. 对mCachedViews中保存的View,设置FLAG_UPDATE和FLAG_INVALID标签,甚至用RecyclerPool回收这些View
    mRecycler.markKnownViewsInvalid();
}
复制代码

markKnownViewsInvalid()处理的目标不只仅只有RecyclerView的子View,并且还包括mCachedViews缓存中的View。性能

界面因为滑动,致使某些不可见的子View被移除,而且优先使用mCachedViews缓存它。当再次因为滑动须要显示这个子View时,就会从mCachedViews中获取。优化

markKnownViewsInvalid()作了两件事

  1. 标记View为FLAG_UPDATEFLAG_INVALID
  2. 标记View的ItemDecoration区域为dirty,也就是设置View的布局参数的mInsetsDirty值为true,表示须要刷新View的ItemDecoration区域。

刷新界面

预处理工做作完了,就会调用requestLayout()来从新布局,咱们把主要精力放在layout过程。

layout过程分为了三步,dispatchLayoutStep1()处理更新操做以及保存动画信息,dispatchLayoutStep3()执行动画并作一些清理工做,而dispatchLayoutStep2()是完成了数据刷新的工做。

dispatchLayoutStep2()把子View的布局工做交给了LayoutManager#onLayoutChildren()完成,这里以LinearLayoutManager#onLayoutChidren()为例进行分析。

public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
    // ...
    
    // 1. 分离/废弃并缓存子View
    detachAndScrapAttachedViews(recycler);
    // 2. 填充子View,由layoutChunk()实现
    fill(recycler, mLayoutState, state, false);
    
    // ...
}
复制代码

LinearLayoutManager对子View的布局工做大体分为两步。首先是分离/移除子View,并缓存它。而后是获取子View并填充给RecyclerView

缓存

首先咱们来分析分离/移除和缓存这一步,调用的是LayoutManager#detachAndScrapAttachedViews()方法,它会遍历RecyclerView全部子View,而后调用LayoutManager#scrapOrRecycleView()方法来执行分离/移除和缓存子View

private void scrapOrRecycleView(Recycler recycler, int index, View view) {
    final ViewHolder viewHolder = getChildViewHolderInt(view);
    if (viewHolder.shouldIgnore()) {
        return;
    }
    // 如下条件是知足的
    // 子View的ViewHolder设置过FLAG_INVALID
    // 子View的ViewHolder没有设置过FLAG_REMOVED
    // Adapter默认没有开启stable id功能
    if (viewHolder.isInvalid() && !viewHolder.isRemoved()
            && !mRecyclerView.mAdapter.hasStableIds()) {
        // 1. 从RecyclerView中移除子View
        removeViewAt(index);
        // 2. Recycler回收子View
        recycler.recycleViewHolderInternal(viewHolder);
    } else {
        detachViewAt(index);
        recycler.scrapView(view);
        mRecyclerView.mViewInfoStore.onViewDetached(viewHolder);
    }
}
复制代码

在预处理工做环节,把View标记为FLAG_INVALID,所以就会先把这个View从RecyclerView中移除,使用的是ViewGroup#removeViewAt()方法。而后使用Recycler来回收这些被移除的子View。

咱们如今来看下回收的过程

void recycleViewHolderInternal(ViewHolder holder) {
            // ...
            
            if (forceRecycle || holder.isRecyclable()) {
                // 因为View被标记为FLAG_INVALID,因此没法使用mCachedViews这个缓存
                if (mViewCacheMax > 0
                        && !holder.hasAnyOfTheFlags(ViewHolder.FLAG_INVALID
                        | ViewHolder.FLAG_REMOVED
                        | ViewHolder.FLAG_UPDATE
                        | ViewHolder.FLAG_ADAPTER_POSITION_UNKNOWN)) {
                    // ...
                }
                // 不能使用mCachedViews这个缓存,就交给RecyclerPool回收
                if (!cached) {
                    addViewHolderToRecycledViewPool(holder, true);
                    recycled = true;
                }
            } else {

            }
            
            // ...
        }
复制代码

因为子View被标记为FLAG_INVALID,所以子View只能交给RecyclerPool进行回收。

填充

如今全部子View都被RecyclerPool回收了,那么接下来分析如何给RecyclerView填充子View。这一步是由LinearLayoutManager#layoutChunk()实现的

void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state, LayoutState layoutState, LayoutChunkResult result) {
    // ...
        
    // 1. 从Recycler中获取View
    View view = layoutState.next(recycler);
        
    // 2. 把子View添加到RecyclerView中
    addView(view);
    
    // 3. 测量子View
    measureChildWithMargins(view, 0, 0);
    
    // 4. 布局子View
    layoutDecoratedWithMargins(view, left, top, right, bottom);
        
    // ...
}
复制代码

填充子View经历了这四步,可是咱们把目光放在如何从Recycler中获取View。它是由Recycler#tryGetViewHolderForPositionByDeadline()实现的

ViewHolder tryGetViewHolderForPositionByDeadline(int position, boolean dryRun, long deadlineNs) {
    boolean fromScrapOrHiddenOrCache = false;
    ViewHolder holder = null;
            
    // 1. 首先从mChangedScrap中获取
    // LinearLayoutManager在大部分状况下是支持predictive item animations
    if (mState.isPreLayout()) {
        holder = getChangedScrapViewForPosition(position);
        fromScrapOrHiddenOrCache = holder != null;
    }

    // 2. 从mAttachedScrap, hidden view, mCachedViews中获取
    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();
                    } else if (holder.wasReturnedFromScrap()) {
                        holder.clearReturnedFromScrapFlag();
                    }
                    recycleViewHolderInternal(holder);
                }
                holder = null;
            } else {
                fromScrapOrHiddenOrCache = true;
            }
        }
    }

    if (holder == null) {
        final int offsetPosition = mAdapterHelper.findPositionOffset(position);
        final int type = mAdapter.getItemViewType(offsetPosition);
        // 3. 经过stable id从mAttachedScrap, mCachedViews中获取
        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) {
            // 4. 从自定义缓存中获取
            final View view = mViewCacheExtension
                    .getViewForPositionAndType(this, position, type);
            if (view != null) {
                holder = getChildViewHolder(view);
            }
        }
        if (holder == null) { // fallback to pool
            // 5. 从RecyclerPool中获取
            holder = getRecycledViewPool().getRecycledView(type);
            if (holder != null) {
                // 重置全部的标志位
                holder.resetInternal();
                // ...
            }
        }
        if (holder == null) {
            // 6. 利用Adapter建立
            holder = mAdapter.createViewHolder(RecyclerView.this, type);
        }
    }

    // ...

    // 7. 根据条件决定是否执行绑定操做
    boolean bound = false;
    if (mState.isPreLayout() && holder.isBound()) {
        // 若是已经绑定,就只更新predictive item animations的位置信息
        holder.mPreLayoutPosition = position;
    } else if (!holder.isBound() || holder.needsUpdate() || holder.isInvalid()) {
        // 若是没有绑定,就须要执行绑定操做
        final int offsetPosition = mAdapterHelper.findPositionOffset(position);
        bound = tryBindViewHolderByDeadline(holder, offsetPosition, position, deadlineNs);
    }

    // 8. 校订布局参数,并更新它
    // ...

    return holder;
}
复制代码

这里我把全部的获取路径都标注了,可是因为View被标记为FLAG_INVALID,因此只能从RecyclerPool进行获取(第6步)。而从RecylerPool获取ViewHolder后,它的全部标志位都被重置了,所以还须要进行绑定(第7步)。

获取到了View,就把它添加到RecyclerView中,而后测量、布局、绘制,所以完成了界面刷新过程。

小结

如今咱们来总结下Adapter#notifyDataSetChanged()方法的优缺点。

优势就是简单无脑。缺点就是影响绘制性能,由于它要把全部子View移除、回收、获取、再绑定。

而在实际中,数据每每只改变一小部分,例如某几项数据更新了,某几项数据删除了等等。这个时候咱们但愿只刷新受影响的子View便可,而不是指望全部子View都刷新。

Adapter提供了不少局部刷新的方法,例如notifyItemChanged()用来处理数据更新,notifyItemInserted()处理数据增长,notifyItemRemoved()处理数据移除,notifyItemMove()处理数据移动,而且还提供了相应的范围操做的方法notifyRangXXX()。这样咱们就没必要无脑使用notifyDataSetChanged()方法,可是须要咱们本身比较数据,而后决定调用那种布局刷新的方法。

局部刷新

全局刷新影响绘制性能,那么咱们来看看局部刷新是如何优化绘制性能的。

咱们挑选Adapter#notifyItemChanged()方法来分析,它会调用观察者的onItemRangeChanged()方法

private class RecyclerViewDataObserver extends AdapterDataObserver {
    public void onItemRangeChanged(int positionStart, int itemCount, Object payload) {
        // 1. 通知AdapterHelper,某个范围内数据有更新
        if (mAdapterHelper.onItemRangeChanged(positionStart, itemCount, payload)) {
            // 2. 触发更新
            triggerUpdateProcessor();
        }
    }
}
复制代码

首先会通知AdapterHelper,某个范围的数据有改变

boolean onItemRangeChanged(int positionStart, int itemCount, Object payload) {
    if (itemCount < 1) {
        return false;
    }
    // 保存UPDATE操做
    mPendingUpdates.add(obtainUpdateOp(UpdateOp.UPDATE, positionStart, itemCount, payload));
    // 保存操做类型
    mExistingUpdateTypes |= UpdateOp.UPDATE;
    // 当只有一个待处理的更新操做时,表示须要当即处理
    return mPendingUpdates.size() == 1;
}
复制代码

AdapterHelper会建立一个相应操做类型的UpdateOp对象保存,而后也会保存这次操做的类型。

从返回值能够看出,若是等待更新的操做只有一个,就表明须要理解处理。咱们假设如今只有一个更新操做,那么会调用triggerUpdateProcessor()来处理

void triggerUpdateProcessor() {
    // POST_UPDATES_ON_ANIMATION在sdk大于16的时候为true
    // mHasFixedSize表示是否有固定尺寸
    // mIsAttached表示是否RecyclerView是否添加到Window中
    if (POST_UPDATES_ON_ANIMATION && mHasFixedSize && mIsAttached) {
        ViewCompat.postOnAnimation(RecyclerView.this, mUpdateChildViewsRunnable);
    } else {
        mAdapterUpdateDuringMeasure = true;
        requestLayout();
    }
}
复制代码

触发更新操做受条件限制,这里的限制条件基本上只有mHasFixedSize,它是经过RecyclerView#setHasFixedSize()设置的。若是RecyclerView的宽高设置为固定尺寸,例如100dp,或者match_parent,那么能够调用setHasFixedSize(true)设置RecyclerView有固定宽高。这样能够在某些时候避免RecyclerView自我测量这一步。

可是不管使用哪一种方式执行更新操做,都会经历layout过程。在dispatchLayoutStep1()中会调用processAdapterUpdatesAndSetAnimationFlags()处理这些更新操做,而且决定是否执行动画。

本文不分析动画部分的源码。

private void processAdapterUpdatesAndSetAnimationFlags() {
    // LinearLayoutManager若是不是在状态恢复中,是支持可预测动画特性的
    if (predictiveItemAnimationsEnabled()) {
        mAdapterHelper.preProcess();
    } else {
        mAdapterHelper.consumeUpdatesInOnePass();
    }
    // 省略动画相关的代码...
}
复制代码

这里又根据LayoutManager是否支持Predictive item animations,份内了两种处理方式,可是它们异曲同工,最终它们都会处理受影响的子View。

Predictive item animations: 对于添加,移除,移动操做(不包括改变操做),会自动建立一个动画,这个动画会显示View从哪里来,到哪里去。

对于数据改变操做,processAdapterUpdatesAndSetAnimationFlags()最终会经过AdapterHelper的以下代码来处理受影响的子View

mCallback.markViewHoldersUpdated(op.positionStart, op.itemCount, op.payload);
复制代码

这个mCallback是在RecyclerView中实现的

void initAdapterManager() {
    mAdapterHelper = new AdapterHelper(new AdapterHelper.Callback() {
        public void markViewHoldersUpdated(int positionStart, int itemCount, Object payload) {
            // 处理范围数据改变操做
            viewRangeUpdate(positionStart, itemCount, payload);
            // 表示是数据改变操做
            mItemsChanged = true;
        }
    }
}
复制代码

viewRangeUpdate()处理了范围数据改变的操做,比较简单,总结以下

  1. RecyclerView处于数据改变范围内的子View,被标记为FLAG_UPDATE,而且标记它的ItemDecoration区域为dirty
  2. mCachedViews中缓存的,且处于数据改变范围内View,被标记为FLAG_UPDATE,而且被RecyclerPool回收。

如今,dispatchLayoutStep1()已经把受数据改变影响的View(包括mCachedView缓存的)所有标记为FLAG_UPDATE,而后在dispatchLayoutStep2()中为子View进行从新布局,它是由LayoutManager#onLayoutChildren()实现的,咱们这里仍然以LinearLayoutManager#onLayoutChildren()为例进行分析。

public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
    // ...
    
    // 1. 分离/废弃并缓存子View
    detachAndScrapAttachedViews(recycler);
    // 2. 填充子View,由layoutChunk()实现
    fill(recycler, mLayoutState, state, false);
    
    // ...
}
复制代码

这段代码是否是似曾相识,没错,咱们刚在前面分析过,只不过此次分析的状况是局部数据改变,而非全局刷新。

缓存

首先咱们来看下如何分离/废弃并缓存子View的。

RecyclerView#detachAndScrapAttachedViews()会遍历全部子View,而后经过ReyclerView#scrapOrRecycleView()来处理

private void scrapOrRecycleView(Recycler recycler, int index, View view) {
    final ViewHolder viewHolder = getChildViewHolderInt(view);
    if (viewHolder.shouldIgnore()) {
        return;
    }
    // 如今的状况是View只被标记为FLAG_UPDATE
    if (viewHolder.isInvalid() && !viewHolder.isRemoved()
            && !mRecyclerView.mAdapter.hasStableIds()) {

    } else {
        // 1. 从RecyclerView分离子View
        detachViewAt(index);
        // 2. 缓存被分离的子View
        recycler.scrapView(view);
        // 为动画保存信息
        mRecyclerView.mViewInfoStore.onViewDetached(viewHolder);
    }
}
复制代码

如今的状况是View只被标记为FLAG_UPDATE,这与全局刷新的状况不同了,这里第一步是把子View从RecyclerView中分离(detach)而不是移除(remove)。它经过RecyclerView中的以下回调实现

private void initChildrenHelper() {
        mChildHelper = new ChildHelper(new ChildHelper.Callback() {
            public void detachViewFromParent(int offset) {
                final View view = getChildAt(offset);
                if (view != null) {
                    final ViewHolder vh = getChildViewHolderInt(view);
                    if (vh != null) {
                        // 1.添加FLAG_TMP_DETACHED标志位
                        vh.addFlags(ViewHolder.FLAG_TMP_DETACHED);
                    }
                }
                // 2.调用ViewGroup#detachViewFromParent()分离子View
                RecyclerView.this.detachViewFromParent(offset);
            }
        }
    }
复制代码

首先把子View标记为FLAG_TMP_DETACHED,而后分离子View。

移除(remove)和分离(detach)有何区别?

  1. 移除会致使从新布局,也就是requestLayout()
  2. 分离只是从ViewGroup#mChildren数组中移除引用,可是必须在同一个绘制周期内,把分离的View从新附着上去或者删除。所以并不会引起从新布局。

如今全部的子View都已经从RecyclerView中分离了,接下来就会使用Recycler来缓存它们,调用的是RecyclerView#scrapView()方法

void scrapView(View view) {
    final ViewHolder holder = getChildViewHolderInt(view);
    // 根据View的状态标记,用不一样方式缓存
    if (holder.hasAnyOfTheFlags(ViewHolder.FLAG_REMOVED | ViewHolder.FLAG_INVALID)
            || !holder.isUpdated() || canReuseUpdatedViewHolder(holder)) {
        holder.setScrapContainer(this, false);
        mAttachedScrap.add(holder);
    } else {
        if (mChangedScrap == null) {
            mChangedScrap = new ArrayList<ViewHolder>();
        }
        holder.setScrapContainer(this, true);
        mChangedScrap.add(holder);
    }
}
复制代码

RecyclerView的全部子View中,对于数据改变范围内的子View,会被标记为FLAG_UPDATE,它会被mChangedScrap缓存,而其余子View会被mAttachedScrap缓存。

填充

如今咱们已经了解了局部刷新的缓存是如何使用的,那么如今咱们来看看LinearLayoutManager是如何实现子View填充的,它是由LinearLayoutManager#layoutChunk()实现的

void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state, LayoutState layoutState, LayoutChunkResult result) {
    // ...
        
    // 1. 从Recycler中获取View
    View view = layoutState.next(recycler);
        
    // 2. 把子View添加/附着到RecyclerView中
    addView(view);
    
    // 3. 测量子View
    measureChildWithMargins(view, 0, 0);
    
    // 4. 布局子View
    layoutDecoratedWithMargins(view, left, top, right, bottom);
        
    // ...
}
复制代码

又是一段熟悉的代码,咱们仍是把目光聚焦到如何从Recycler获取子View的过程,它是经过Recycler#tryGetViewHolderForPositionByDeadline()实现的

ViewHolder tryGetViewHolderForPositionByDeadline(int position, boolean dryRun, long deadlineNs) {
    boolean fromScrapOrHiddenOrCache = false;
    ViewHolder holder = null;
            
    // 1. 首先从mChangedScrap中获取
    // LinearLayoutManager在大部分状况下是支持predictive item animations
    if (mState.isPreLayout()) {
        holder = getChangedScrapViewForPosition(position);
        fromScrapOrHiddenOrCache = holder != null;
    }

    // 2. 从mAttachedScrap, hidden view, mCachedViews中获取
    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();
                    } else if (holder.wasReturnedFromScrap()) {
                        holder.clearReturnedFromScrapFlag();
                    }
                    recycleViewHolderInternal(holder);
                }
                holder = null;
            } else {
                fromScrapOrHiddenOrCache = true;
            }
        }
    }

    if (holder == null) {
        final int offsetPosition = mAdapterHelper.findPositionOffset(position);
        final int type = mAdapter.getItemViewType(offsetPosition);
        // 3. 经过stable id从mAttachedScrap, mCachedViews中获取
        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) {
            // 4. 从自定义缓存中获取
            final View view = mViewCacheExtension
                    .getViewForPositionAndType(this, position, type);
            if (view != null) {
                holder = getChildViewHolder(view);
            }
        }
        if (holder == null) { // fallback to pool
            // 5. 从RecyclerPool中获取
            holder = getRecycledViewPool().getRecycledView(type);
            if (holder != null) {
                // 重置全部的标志位
                holder.resetInternal();
                // ...
            }
        }
        if (holder == null) {
            // 6. 利用Adapter建立
            holder = mAdapter.createViewHolder(RecyclerView.this, type);
        }
    }

    // ...

    // 7. 根据条件决定是否执行绑定操做
    boolean bound = false;
    if (mState.isPreLayout() && holder.isBound()) {
        // 若是已经绑定,就只更新predictive item animations的位置信息
        holder.mPreLayoutPosition = position;
    } else if (!holder.isBound() || holder.needsUpdate() || holder.isInvalid()) {
        // 若是没有绑定,就须要执行绑定操做
        final int offsetPosition = mAdapterHelper.findPositionOffset(position);
        bound = tryBindViewHolderByDeadline(holder, offsetPosition, position, deadlineNs);
    }

    // 8. 校订布局参数,并更新它
    // ...

    return holder;
}
复制代码

对于部分数据改变操做,被分离的子View,只被标记为FLAG_UPDATEFLAG_TMP_DETACHED,所以能用到的获取View的途径只有第1步和第2步,也就是从mChangedScrapmAttachedScrap中获取。然而,对于其余的操做,例如添加,移除,移动,可能就会从不一样路径获取View。

对于数据改变这种状况,从mChangedScrapmAttachedScrap中获取ViewHolder的条件基本上只要知足一个条件便可,这个条件是从ViewHolder获取的位置要与填充的位置相等。

mChangedScrapmAttachedScrap中获取ViewHolder后,它仍是已经绑定的状态。可是对于数据改变受影响的子View,因为被标记为FALG_UPDATE,所以还须要再绑定一次数据,这样就能够达到数据更新的效果。而对于那些没有受数据改变影响的子View,就不须要再绑定。

如今咱们已经从Recycler中获取了View,而且数据改变的View已经从新绑定数据,如今须要把这些分离的子View从新附着(attach)到RecyclerView上,它是经过LayoutManager#addViewInt()实现的

private void addViewInt(View child, int index, boolean disappearing) {
    final ViewHolder holder = getChildViewHolderInt(child);
    // ...
    final LayoutParams lp = (LayoutParams) child.getLayoutParams();
    if (holder.wasReturnedFromScrap() || holder.isScrap()) {
        if (holder.isScrap()) {
            holder.unScrap();
        } else {
            holder.clearReturnedFromScrapFlag();
        }
        // 处理分离子View的状况,它会把子View从新attach到RecyclerView中
        mChildHelper.attachViewToParent(child, index, child.getLayoutParams(), false);
        if (DISPATCH_TEMP_DETACH) {
            ViewCompat.dispatchFinishTemporaryDetach(child);
        }
    } else if (child.getParent() == mRecyclerView) { // it was not a scrap but a valid child
        // 处理子View移动的状况
        
    } else {
        // 对于其余的状况,都是把子View添加到RecyclerView中
        mChildHelper.addView(child, index, false);
        lp.mInsetsDirty = true;
        if (mSmoothScroller != null && mSmoothScroller.isRunning()) {
            mSmoothScroller.onChildAttachedToWindow(child);
        }
    }
    // ...
}
复制代码

对于由于数据改变而分离的子View,会从新附着(attach)到RecyclerView上。然而对于移动,添加操做,还会有不一样的操做,这里就不依依分析了。

如今被分离的子View已经从新附着到RecyclerView上,而且数据改变的部分也相应的更新了,剩下的就是绘制工做了。

小结

局部刷新,是以分离和再附着的方式处理那些不受影响的子View,而只处理受影响的子View,或从新绑定后再附着,或直接建立在添加到RecyclerView。总之,相对于全局刷新,提高了绘制性能。

DiffUtil

要使用局部刷新,就必须比较先后的数据差别,而后决定使用哪一种数据刷新方式。比较这个过程每每是复杂的,因此后来Google又推出了一个工具类DiffUtil,它把这个比较过程抽象出来,经过它能够计算先后数据差别,而后精准的调用局部刷新的方法。

若是以为个人文章写的还不错,点个赞,关注我。

相关文章
相关标签/搜索