相关文章推荐
冷静的树叶  ·  Error Handling with ...·  9 月前    · 
酒量大的足球  ·  Android - ...·  1 年前    · 

设置数据观察者

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

首先来分析最简单粗暴,而且也是最常用的数据刷新方法, Adapter#notifyDataSetChanged() ,它表示数据完全改变,界面需要全局刷新。

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

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为无效的。

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() 做了两件事

  • 标记View为 FLAG_UPDATE FLAG_INVALID
  • 标记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() 处理了范围数据改变的操作,比较简单,总结如下

  • RecyclerView 处于数据改变范围内的子View,被标记为 FLAG_UPDATE ,并且标记它的 ItemDecoration 区域为 dirty
  • 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 )有何区别?

  • 移除会导致重新布局,也就是 requestLayout()
  • 分离只是从 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_UPDATE FLAG_TMP_DETACHED ,因此能用到的获取View的途径只有第1步和第2步,也就是从 mChangedScrap mAttachedScrap 中获取。然而,对于其他的操作,例如添加,移除,移动,可能就会从不同路径获取View。

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

    mChangedScrap mAttachedScrap 中获取 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 ,它把这个比较过程抽象出来,通过它可以计算前后数据差异,然后精准的调用局部刷新的方法。

    如果觉得我的文章写的还不错,点个赞,关注我。

    分类:
    Android
  •