RecyclerView.Adapter根据数据变化更新UI原理

  • 概述

    当列表数据发生变化时,需要调用Adapter中的一些API来通知RecyclerView改变UI,比如列表刷新、添加、移除、部分数据项改变等,在这些数据更改时需要告诉RecyclerView从而做出改变,本文会根据常用数据更新场景来分析一下是怎么做到的。

    在开始之前,我们要确定一个规范,那就是一定要是数据驱动UI,在onBindViewHolder中进行数据绑定的时候把UI的所有组件的状态和对应数据段对应在一起,然后当数据改变时,我们再调用相关API通知数据变化,这时会重新走onBindViewHolder方法给对应组件重新赋值,这就保证了数据源一致性,切不可在onCreateViewHolder或者onBindViewHolder中根据不同情况动态的设置组件状态,不然就会导致多个地方可以修改组建,造成难以捕捉的混乱错误。

    1. notifyDataSetChanged方法,在所有数据项替换时需要调用,比如刷新操作,当然,所有情况的数据改变你都可以通过这个方法更新UI,但是为了某几项的数据改变去更新所有项显然会影响性能,所以这才有了下面的这些方法,用于更新一部分项;
    2. notifyItemChanged方法,在某一项数据改变时调用;
    3. notifyItemRangeChanged方法,在连续的一部分项数据改变时调用;
    4. notifyItemInserted方法,在插入某一项数据时调用;
    5. notifyItemMoved方法,在移动某一项数据的位置时调用,传入一个当前位置参数和一个目标位置参数;
    6. notifyItemRangeInserted方法,在插入连续的一部分项的数据时调用;
    7. notifyItemRemoved方法,在移除某一项数据时调用;
    8. notifyItemRangeRemoved方法,在移除连续的一部分项数据时调用。

    上述这些方法内部都是调用了mObservable的同名方法,mObservable是什么呢?它在创建Adapter实例时自动创建:

    private final AdapterDataObservable mObservable = new AdapterDataObservable();
    

    AdapterDataObservable继承自Observable,mObservers中存放了多个AdapterDataObserver,上述的每个方法都会调用下面的循环代码去调用每一个AdapterDataObserver的同名方法:

    for (int i = mObservers.size() - 1; i >= 0; i--) {
        mObservers.get(i).onXxxx(...);
    }
    

    mObservers是通过Observable的registerObserver方法注册的,这个方法在Adapter的registerAdapterDataObserver中调用,在setAdapter流程中会调用这个方法注册:

    adapter.registerAdapterDataObserver(mObserver);
    

    mObserver是在RecyclerView实例化的时候自动创建的:

    private final RecyclerViewDataObserver mObserver = new RecyclerViewDataObserver();
    

    在RecyclerViewDataObserver中,和AdapterDataObservable类似,每个方法都是调用了mAdapterHelper的同名方法:

    assertNotInLayoutOrScroll(null);
    if (mAdapterHelper.onXxx(...)) {
        triggerUpdateProcessor();
    }
    

    triggerUpdateProcessor方法就是通知View树重新layout:

    void triggerUpdateProcessor() {
        if (POST_UPDATES_ON_ANIMATION && mHasFixedSize && mIsAttached) {
            ViewCompat.postOnAnimation(RecyclerView.this, mUpdateChildViewsRunnable);
        } else {
            mAdapterUpdateDuringMeasure = true;
            requestLayout();
        }
    }
    

    特殊的notifyDataSetChanged调用的是onChanged方法:

    @Override
    public void onChanged() {
        assertNotInLayoutOrScroll(null);
        mState.mStructureChanged = true;
    
        processDataSetCompletelyChanged(true);
        if (!mAdapterHelper.hasPendingUpdates()) {
            requestLayout();
        }
    }
    

    下面逐一分析mAdapterHelper的这些方法。

  • 全部更新 — processDataSetCompletelyChanged

    根据上面的梳理,我们知道notifyDataSetChanged方法调用最后会调用RecyclerViewDataObserver的onChange方法,这里面会调用processDataSetCompletelyChanged方法:

    void processDataSetCompletelyChanged(boolean dispatchItemsChanged) {
        mDispatchItemsChangedEvent |= dispatchItemsChanged;
        mDataSetHasChangedAfterLayout = true;
        markKnownViewsInvalid();
    }
    

    这里调用了markKnownViewsInvalid方法:

    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()) {
                holder.addFlags(ViewHolder.FLAG_UPDATE | ViewHolder.FLAG_INVALID);
            }
        }
        markItemDecorInsetsDirty();
        mRecycler.markKnownViewsInvalid();
    }
    

    mChildHelper.getUnfilteredChildCount得到的是所有已经attach到RecyclerView的ViewHolder,包括隐藏的,这里会给所有attach的ViewHolder设置FLAG_UPDATE和FLAG_INVALID属性,markItemDecorInsetsDirty方法会把这些ViewHolder的itemView的decorInset(即padding这些)设置为dirty,markKnownViewsInvalid会清除mCacheViews并且把它里面的ViewHolder放到mScrapHeap中去。

    待到requestLayout触发新数据布局的时候走到tryGetViewHolderForPositionByDeadline方法中,因为mCacheViews中清空了,所以此时只有getRecycledViewPool().getRecycledView(type)取到的mScrapHeap中有之前缓存的ViewHolder,还记得它的默认大小是5吗,所以滚动显示前5个表项的时候是不会走onCreateViewHolder的,注意这里的5个会包括可以在屏幕中显示的,因为重新设置新的数据内容,相当于所有的表项都会销毁,所以当前在屏幕中的也会被缓存,而滚动的时候则只有在超出屏幕某个范围之后才会缓存。

    不管是不是重新构造的ViewHolder,都会走onBindViewHolder方法,因为之前markKnownViewsInvalid中把当时所有attach的View都会添加FLAG_UPDATE和FLAG_INVALID标志,而在tryGetViewHolderForPositionByDeadline中会根据这些标志调用onBindViewHolder:

    if (!holder.isBound() || holder.needsUpdate() || holder.isInvalid()) {
        final int offsetPosition = mAdapterHelper.findPositionOffset(position);
        bound = tryBindViewHolderByDeadline(holder, offsetPosition, position, deadlineNs);
    }
    
  • 部分更新

    boolean onItemRangeChanged(int positionStart, int itemCount, Object payload) {
        if (itemCount < 1) {
            return false;
        }
        mPendingUpdates.add(obtainUpdateOp(UpdateOp.UPDATE, positionStart, itemCount, payload));
        mExistingUpdateTypes |= UpdateOp.UPDATE;
        return mPendingUpdates.size() == 1;
    }
    

    Adapter中的notifyItemChanged、notifyItemRangeChanged其实都是走的这个方法,只不过notifyItemChanged传入的itemCount固定为1。

    obtainUpdateOp方法如下:

    @Override
    public UpdateOp obtainUpdateOp(int cmd, int positionStart, int itemCount, Object payload) {
        UpdateOp op = mUpdateOpPool.acquire();
        if (op == null) {
            op = new UpdateOp(cmd, positionStart, itemCount, payload);
        } else {
            op.cmd = cmd;
            op.positionStart = positionStart;
            op.itemCount = itemCount;
            op.payload = payload;
        }
        return op;
    }
    

    现在我们知道了onItemRangeChanged其实就是在mPendingUpdates中添加一个UpdateOp。

    其他的onItemRangeInserted、onItemRangeRemoved和onItemRangeMoved都是调用obtainUpdateOp在mPendingUpdates中添加一个UpdateOp,但是UpdateOp的cmd属性分别设置为UpdateOp.ADD、UpdateOp.REMOVE、UpdateOp.MOVE。

    然后调用requestLayout之后就会调用onLayout方法,onLayout中调用了dispatchLayout方法,部分代码如下:

    if (mState.mLayoutStep == State.STEP_START) {
        dispatchLayoutStep1();
        mLayout.setExactMeasureSpecsFrom(this);
        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中一开始会把dispatchLayoutStep3置为State.STEP_START,它的默认值也是State.STEP_START,所以在开始一次全新的layout的时候一定会是State.STEP_START的,那么就会调用dispatchLayoutStep1方法,这个方法中会调用processAdapterUpdatesAndSetAnimationFlags方法,里面有一段:

    if (predictiveItemAnimationsEnabled()) {
        mAdapterHelper.preProcess();
    } else {
        mAdapterHelper.consumeUpdatesInOnePass();
    }
    

    predictiveItemAnimationsEnabled会决定布局是否支持动画更新:

    private boolean predictiveItemAnimationsEnabled() {
        return (mItemAnimator != null && mLayout.supportsPredictiveItemAnimations());
    }
    

    mItemAnimator会自动初始化为DefaultItemAnimator,所以一定不为null,所以这里取决于LayoutManager的supportsPredictiveItemAnimations方法返回,默认返回false,LinearLayoutManager中重写了它:

    @Override
    public boolean supportsPredictiveItemAnimations() {
        return mPendingSavedState == null && mLastStackFromEnd == mStackFromEnd;
    }
    

    mPendingSavedState在onRestoreInstanceState恢复中才会赋值,在onLayoutCompleted布局完成中会置为null,那这里的意思就是在界面恢复过程中不需要展示动画,可以理解,因为界面恢复工作是为了默默地恢复之前的界面,最好不要让用户感知到,加上动画是此地无银了。还有一个条件就是布局方向和上一次相比没有发生变化,比如上次是从上而下的顺序,插入一个表项之后改成从下往上填充了,那这种情况也不会加上动画。

    界面恢复和临时更改布局方向都是很少出现的情况,所以默认都是会走preProcess逻辑,但是这里consumeUpdatesInOnePass的逻辑我也会列出来,我们先看preProcess。

  • mAdapterHelper.preProcess()

    void preProcess() {
        mOpReorderer.reorderOps(mPendingUpdates);
        final int count = mPendingUpdates.size();
        for (int i = 0; i < count; i++) {
            UpdateOp op = mPendingUpdates.get(i);
            switch (op.cmd) {
                case UpdateOp.ADD:
                    applyAdd(op);
                    break;
                case UpdateOp.REMOVE:
                    applyRemove(op);
                    break;
                case UpdateOp.UPDATE:
                    applyUpdate(op);
                    break;
                case UpdateOp.MOVE:
                    applyMove(op);
                    break;
            }
            if (mOnItemProcessedCallback != null) {
                mOnItemProcessedCallback.run();
            }
        }
        mPendingUpdates.clear();
    }
    

    这个方法中会根据不同操作调用不同方法。

    • 添加操作 — applyAdd方法

      private void applyAdd(UpdateOp op) {
          postponeAndUpdateViewHolders(op);
      }
      

      这里直接调用了postponeAndUpdateViewHolders方法:

      private void postponeAndUpdateViewHolders(UpdateOp op) {
          if (DEBUG) {
              Log.d(TAG, "postponing " + op);
          }
          mPostponedList.add(op);
          switch (op.cmd) {
              case UpdateOp.ADD:
                  mCallback.offsetPositionsForAdd(op.positionStart, op.itemCount);
                  break;
              case UpdateOp.MOVE:
                  mCallback.offsetPositionsForMove(op.positionStart, op.itemCount);
                  break;
              case UpdateOp.REMOVE:
                  mCallback.offsetPositionsForRemovingLaidOutOrNewView(op.positionStart,
                          op.itemCount);
                  break;
              case UpdateOp.UPDATE:
                  mCallback.markViewHoldersUpdated(op.positionStart, op.itemCount, op.payload);
                  break;
              default:
                  throw new IllegalArgumentException("Unknown update op type for " + op);
          }
      }
      

      当UpdateOp的cmd属性是UpdateOp.ADD的时候会调用Callback.offsetPositionsForAdd(op.positionStart, op.itemCount)方法,这个方法中会调用offsetPositionRecordsForInsert方法:

    void offsetPositionRecordsForInsert(int positionStart, int itemCount) {
    final int childCount = mChildHelper.getUnfilteredChildCount();
    for (int i = 0; i < childCount; i++) {
    final ViewHolder holder = getChildViewHolderInt(mChildHelper.getUnfilteredChildAt(i));
    if (holder != null && !holder.shouldIgnore() && holder.mPosition >= positionStart) {
    if (DEBUG) {
    Log.d(TAG, "offsetPositionRecordsForInsert attached child " + i + " holder "
    + holder + " now at position " + (holder.mPosition + itemCount));
    }
    holder.offsetPosition(itemCount, false);
    mState.mStructureChanged = true;
    }
    }
    mRecycler.offsetPositionRecordsForInsert(positionStart, itemCount);
    requestLayout();
    }

    
    这个方法做了两件事,一件是通过getUnfilteredChildAt方法循环获取已经attach的ViewHolder,凡是holder.mPosition大于positionStart的都是在插入元素之后的表项,offsetPosition把这些ViewHolder的mPosition属性都在原来基础上加上itemCount,从而达到在插入元素之后的表项同步位移的效果;另一件事就是调用Recycler的offsetPositionRecordsForInsert方法修改mCacheViews中的ViewHolder的mPosition属性,也就是同步操作,**注意,这里并没有同步其他的缓存集合,所以那些缓存集合中的mPosition和修改后的位置就不匹配了,这就会导致那些缓存失效**。
    
    offsetPosition里修改mPosition代码:**mPosition += offset**。
    
    其他的移动、删除、更新操作的原理都和这里的添加操作同理,下面就不多说了,只是由于它们的操作的目的不同,所以偏移的算法会不一样,下面只会分析它们自己的偏移算法。
    
    + ### 移动操作 — applyMove方法
    
    applyMove方法也是直接调用了postponeAndUpdateViewHolders方法,同理move操作会调用RecyclerView的offsetPositionRecordsForMove方法:
    
    ```java
    void offsetPositionRecordsForMove(int from, int to) {
      final int childCount = mChildHelper.getUnfilteredChildCount();
      final int start, end, inBetweenOffset;
        //start永远是position更小的那个,而end会是更大的那个
        //inBetweenOffset标志着偏移方向,上移就是减1,下移就是加1
      if (from < to) {
          start = from;
          end = to;
          inBetweenOffset = -1;
      } else {
          start = to;
          end = from;
        inBetweenOffset = 1;
      }
    
      for (int i = 0; i < childCount; i++) {
          final ViewHolder holder = getChildViewHolderInt(mChildHelper.getUnfilteredChildAt(i));
            //根据start和end锁定偏移范围,mPosition小于start和大于end的表项不移动
          if (holder == null || holder.mPosition < start || holder.mPosition > end) {
              continue;
          }
          if (DEBUG) {
              Log.d(TAG, "offsetPositionRecordsForMove attached child " + i + " holder "
                      + holder);
          }
          //找到当前需要移动的目标ViewHolder了,to - from直接算出带方向的偏移距离,mPosition加上这个值就是偏移后的下标
          if (holder.mPosition == from) {
              holder.offsetPosition(to - from, false);
          } else {
                //start和end之间的表项位移
            holder.offsetPosition(inBetweenOffset, false);
          }
    
          mState.mStructureChanged = true;
      }
    mRecycler.offsetPositionRecordsForMove(from, to);
      requestLayout();
    }
    

    Recycler的offsetPositionRecordsForMove方法里面和这里的算法一样,只不过它的操作对象是mCacheView。

    • 更新操作 — applyUpdate方法

      applyUpdate方法里会计算出attach的ViewHolder中可见的那些的偏移位置,然后调用postponeAndUpdateViewHolders方法,最后会调用到RecyclerView的viewRangeUpdate方法:

      void viewRangeUpdate(int positionStart, int itemCount, Object payload) {
        final int childCount = mChildHelper.getUnfilteredChildCount();
        final int positionEnd = positionStart + itemCount;
      
        for (int i = 0; i < childCount; i++) {
            final View child = mChildHelper.getUnfilteredChildAt(i);
            final ViewHolder holder = getChildViewHolderInt(child);
            if (holder == null || holder.shouldIgnore()) {
                continue;
            }
            if (holder.mPosition >= positionStart && holder.mPosition < positionEnd) {
                // We re-bind these view holders after pre-processing is complete so that
                // ViewHolders have their final positions assigned.
                holder.addFlags(ViewHolder.FLAG_UPDATE);
                holder.addChangePayload(payload);
                // lp cannot be null since we get ViewHolder from it.
                ((LayoutParams) child.getLayoutParams()).mInsetsDirty = true;
            }
        }
        mRecycler.viewRangeUpdate(positionStart, itemCount);
      

    }

    
    这个方法很简单,就是给attach的ViewHolder添加FLAG_UPDATE标签。
    
    + ### 移除操作 — applyRemove方法
    
    这个方法里同样会计算出在要移除的范围内,attach的ViewHolder中可见的那些的偏移位置。然后调用postponeAndUpdateViewHolders方法,最后会调用到RecyclerView的offsetPositionRecordsForRemove方法:
    
    ```java
    void offsetPositionRecordsForRemove(int positionStart, int itemCount,
            boolean applyToPreLayout) {
        final int positionEnd = positionStart + itemCount;
        final int childCount = mChildHelper.getUnfilteredChildCount();
        for (int i = 0; i < childCount; i++) {
            final ViewHolder holder = getChildViewHolderInt(mChildHelper.getUnfilteredChildAt(i));
            if (holder != null && !holder.shouldIgnore()) {
                if (holder.mPosition >= positionEnd) {
                    if (DEBUG) {
                        Log.d(TAG, "offsetPositionRecordsForRemove attached child " + i
                                + " holder " + holder + " now at position "
                                + (holder.mPosition - itemCount));
                    }
                    holder.offsetPosition(-itemCount, applyToPreLayout);
                    mState.mStructureChanged = true;
                } else if (holder.mPosition >= positionStart) {
                    if (DEBUG) {
                        Log.d(TAG, "offsetPositionRecordsForRemove attached child " + i
                                + " holder " + holder + " now REMOVED");
                    }
                    holder.flagRemovedAndOffsetPosition(positionStart - 1, -itemCount,
                            applyToPreLayout);
                    mState.mStructureChanged = true;
                }
            }
        }
        mRecycler.offsetPositionRecordsForRemove(positionStart, itemCount, applyToPreLayout);
        requestLayout();
    }
    

    flagRemovedAndOffsetPosition方法如下:

    void flagRemovedAndOffsetPosition(int mNewPosition, int offset, boolean applyToPreLayout) {
        addFlags(ViewHolder.FLAG_REMOVED);
        offsetPosition(offset, applyToPreLayout);
        mPosition = mNewPosition;
    }
    

    注意,这里添加了FLAG_REMOVED标签,这里调用的offsetPosition方法中:

    if (applyToPreLayout) {
        mPreLayoutPosition += offset;
    }
    mPosition += offset;
    

    因为这里的applyToPreLayout传进来的是false,所以mPreLayoutPosition不会同步到最新位置,还是保留之前表项的存在位置,这很关键。mPosition会被置为新的移除后的位置,也就是移除的第一个表项的前一个位置。

  • mAdapterHelper.consumeUpdatesInOnePass方法

    void consumeUpdatesInOnePass() {
        // we still consume postponed updates (if there is) in case there was a pre-process call
        // w/o a matching consumePostponedUpdates.
        consumePostponedUpdates();
        final int count = mPendingUpdates.size();
        for (int i = 0; i < count; i++) {
            UpdateOp op = mPendingUpdates.get(i);
            switch (op.cmd) {
                case UpdateOp.ADD:
                    mCallback.onDispatchSecondPass(op);
                    mCallback.offsetPositionsForAdd(op.positionStart, op.itemCount);
                    break;
                case UpdateOp.REMOVE:
                    mCallback.onDispatchSecondPass(op);
                    mCallback.offsetPositionsForRemovingInvisible(op.positionStart, op.itemCount);
                    break;
                case UpdateOp.UPDATE:
                    mCallback.onDispatchSecondPass(op);
                    mCallback.markViewHoldersUpdated(op.positionStart, op.itemCount, op.payload);
                    break;
                case UpdateOp.MOVE:
                    mCallback.onDispatchSecondPass(op);
                    mCallback.offsetPositionsForMove(op.positionStart, op.itemCount);
                    break;
            }
            if (mOnItemProcessedCallback != null) {
                mOnItemProcessedCallback.run();
            }
        }
        recycleUpdateOpsAndClearList(mPendingUpdates);
        mExistingUpdateTypes = 0;
    }
    

    如果在上述调用dispatchLayoutStep2流程中调用的这个方法,因为mPendingUpdates在preProcess中clear了,所以只会走consumePostponedUpdates方法,里面会循环mPostponedList逐个调用mCallback.onDispatchSecondPass方法,mCallback在构造AdapterHelper的时候构造,mCallback.onDispatchSecondPass方法最终调用的是它的dispatchUpdate方法:

    void dispatchUpdate(AdapterHelper.UpdateOp op) {
        switch (op.cmd) {
            case AdapterHelper.UpdateOp.ADD:
                mLayout.onItemsAdded(RecyclerView.this, op.positionStart, op.itemCount);
                break;
            case AdapterHelper.UpdateOp.REMOVE:
                mLayout.onItemsRemoved(RecyclerView.this, op.positionStart, op.itemCount);
                break;
            case AdapterHelper.UpdateOp.UPDATE:
                mLayout.onItemsUpdated(RecyclerView.this, op.positionStart, op.itemCount,
                        op.payload);
                break;
            case AdapterHelper.UpdateOp.MOVE:
                mLayout.onItemsMoved(RecyclerView.this, op.positionStart, op.itemCount, 1);
                break;
        }
    }
    

    发现LinearLayoutManager中并没有实现父类的这些方法,所以这里其实就是给自定义回调处理提供入口而已。

    如果是在dispatchLayoutStep1中调用的这个方法,会根据UpdateOp的cmd调用不同的方法,这和上面的preProcess流程调用的mCallback的方法大部分是完全相同的,只不过remove的时候有所不同,这里remove操作最后调用的也是offsetPositionsForRemovingInvisible方法,只不过这里传入的applyToPreLayout方法是true,所以这里的mPreLayoutPosition也会同步到最新位置,不过暂时mPreLayoutPosition这个值还没发现哪里用到了。

  • 刷新UI

    以上在dispatchLayoutStep1中,preProcess一个主要的工作就是把我们调用Adapter.notifyDataXxx流程中保存的mPendingUpdates给保存到了mPostponedList中,mPendingUpdates的作用主要是用于在preProcess中设置FLAG,但是对我们这里的原理其实帮助不大,可以把它理解成一个摆渡者,把更新信息摆渡给mPostponedList。

    在dispatchLayoutStep2流程中,我们最终会调用tryGetViewHolderForPositionByDeadline方法,在这个方法中有这么一段:

    if (!holder.isBound() || holder.needsUpdate() || holder.isInvalid()) {
        final int offsetPosition = mAdapterHelper.findPositionOffset(position);
        bound = tryBindViewHolderByDeadline(holder, offsetPosition, position, deadlineNs);
    }
    

    这里会通过mAdapterHelper的findPositionOffset方法计算出最新数据的位置:

    int findPositionOffset(int position, int firstPostponedItem) {
        int count = mPostponedList.size();
        for (int i = firstPostponedItem; i < count; ++i) {
            UpdateOp op = mPostponedList.get(i);
            if (op.cmd == UpdateOp.MOVE) {
                if (op.positionStart == position) {
                    position = op.itemCount;
                } else {
                    if (op.positionStart < position) {
                        position--; // like a remove
                    }
                    if (op.itemCount <= position) {
                        position++; // like an add
                    }
                }
            } else if (op.positionStart <= position) {
                if (op.cmd == UpdateOp.REMOVE) {
                    if (position < op.positionStart + op.itemCount) {
                        return -1;
                    }
                    position -= op.itemCount;
                } else if (op.cmd == UpdateOp.ADD) {
                    position += op.itemCount;
                }
            }
        }
        return position;
    }
    

    然后调用tryBindViewHolderByDeadline方法回调到bindViewHolder方法完成新位置的数据更新,之后呢,就交给fill方法进行布局和偏移了。

  • 总结

    纵上可知,数据变化触发RecyclerView更新UI的原理如下:

    1. Adapter中维护了一个AdapterDataObservable实例,可以通过它添加多个响应数据变化的监听器,那么我们这里在setAdapter时(setAdapterInternal中)会设置一个监听器,叫做RecyclerViewDataObserver,调用Adapter的更新数据的方法后,AdapterDataObservable会通知所有监听器数据改变,这里就会调用到RecyclerViewDataObserver的对应方法。

    2. RecyclerViewDataObserver的相关方法中,按照逻辑途径来说有两类操作,一类是全部数据发生改变时调用的notifyDataSetChanged方法的逻辑;另一个是添加、移除、部分change或者移动这些部分更新的逻辑。前者会直接遍历所有目前已经attach的ViewHolder,调用addFlags方法添加ViewHolder.FLAG_UPDATE和ViewHolder.FLAG_INVALI标签;后者会调用AdapterHelper的相关方法来进行处理。但其实最终的最终都是修改ViewHolder的flags。

    3. AdapterHelper中维护了一个ArrayList,叫做mPendingUpdates,这里的对应方法中会构造一个新的UpdateOp或者从pool中取出一个复用,设置UpdateOp的相关属性,其中cmd表示操作类型,有ADD、REMOVE、UPDATE、MOVE四种,positionStart表示要改动的起始位置,itemCount表示从起始位置开始需要修改的表项个数,最后会把UpdateOp对象添加到mPendingUpdates中,mExistingUpdateTypes会通过或操作把下次布局时需要执行的操作类型记录,在合适的时机会调用hasAnyUpdateTypes查看是否含有需要执行的操作类型:

      boolean hasAnyUpdateTypes(int updateTypes) {
          return (mExistingUpdateTypes & updateTypes) != 0;
      }
      

      其实可以通过遍历mPendingUpdates达到一样的效果,这里主要是为了性能考虑。

    4. mPendingUpdates中保存的都是需要更新的ViewHolder,接着会调用requestLayout方法。

    5. dispatchLayoutStep1方法中会调用processAdapterUpdatesAndSetAnimationFlags方法,这里会调用preProcess方法,这个方法首先会把这些UpdateOp保存到mPostponedList中,然后会根据mPendingUpdates中的UpdateOp修改对应的attach的ViewHolder。

    6. notifyDataSetChanged逻辑因为直接修改的FLAG,没有操作mPendingUpdates,所以调用了preProcess也不会发生任何操作。

    7. 然后调用dispatchLayoutStep2方法进行fill布局,在tryGetViewHolderForPositionByDeadline中会从回收区中尝试拿回尚未丢弃的holder,这里其实和ViewHolder是什么样的没有关系,关键的是bindViewHolder方法的position参数是什么,这里会通过mAdapterHelper.findPositionOffset(position)方法遍历mPostponedList,找到对应的UpdateOp,根据它修改当前的position,然后传给bindViewHolder,从而达到数据更新的目的。

    总之,其实中心思想就是给bindViewHolder方法的position找最新的值,最核心的关键就是先保存要修改的position信息,然后在布局的时候根据它去数据集里获取针对这个位置的数据,从而达到更新的效果。ViewHolder拿到了能用就复用,不能用就创建新的,其实preProcess里面那么繁琐的修改,主要是为了之后根据FLAG复用ViewHolder。

你可能感兴趣的:(RecyclerView.Adapter根据数据变化更新UI原理)