RecyclerView源码学习笔记(二)setAdapter

引言

上篇文章RecyclerView源码学习笔记(一)构造函数和setLayoutManager方法主要学习了RecyclerView初始化和setLayoutManager方法的源码,这篇我们学习setAdapter方法的源码

内容

setAdapter方法

按照我们平时最简单的使用习惯,在调用完setLayoutManager方法之后就要调用setAdapter方法了,直接贴源码

    /** * Set a new adapter to provide child views on demand. * <p> * When adapter is changed, all existing views are recycled back to the pool. If the pool has * only one adapter, it will be cleared. * * @param adapter The new adapter to set, or null to set no adapter. * @see #swapAdapter(Adapter, boolean) */
    public void setAdapter(@Nullable Adapter adapter) {
        // bail out if layout is frozen
        setLayoutFrozen(false);
        setAdapterInternal(adapter, false, true);
        processDataSetCompletelyChanged(false);
        requestLayout();
    }

代码不多,从注释来看这个方法的作用是

  • 设置一个新的Adapter
  • 所有已经存在的view将会被回收到pool中,如果pool只有一个adapter,那么这个pool将会被清空

上面提到的pool就是在前一篇RecyclerView源码学习笔记(一)构造函数和setLayoutManager方法中说的RecycledViewPool。
接下来一行一行看下去。第一行是调用了setLayoutFrozen(false),这个方法的作用是什么呢?从注释来看可以将此方法归纳为以下几点:

  • 决定RecyclerView是否可以进行layout和scroll,如果参数是true,相当于这个RecyclerView被冻住了,那么layout的请求将被推迟,直到调用setLayoutFrozen(false)
  • 当RecyclerView被冻住的时候,RecyclerView的smoothScrollByscrollByscrollToPositionsmoothScrollToPosition这些方法的调用将直接被丢弃,也就是直接返回,这个我们可以看一下scrollBy的源码,当判断到mLayoutFrozen等于true的时候就直接返回了,而这个mLayoutFrozen就是在setLayoutFrozen方法中被赋值的。
    public void smoothScrollBy(@Px int dx, @Px int dy, @Nullable Interpolator interpolator) {
        if (mLayout == null) {
            Log.e(TAG, "Cannot smooth scroll without a LayoutManager set. "
                    + "Call setLayoutManager with a non-null argument.");
            return;
        }
        if (mLayoutFrozen) {
            return;
        }
        if (!mLayout.canScrollHorizontally()) {
            dx = 0;
        }
        if (!mLayout.canScrollVertically()) {
            dy = 0;
        }
        if (dx != 0 || dy != 0) {
            mViewFlinger.smoothScrollBy(dx, dy, interpolator);
        }
    }
  • 另外RecyclerView的TouchEvents 和 GenericMotionEvents事件也会被丢弃,LayoutManager的onFocusSearchFailed不会被调用,但是LayoutManager的scrollToPositionsmoothScrollToPosition并不受影响,还可以照常运行。
    setAdapter方法和swapAdapter方法会自动结束冰冻状态,就是会调用setLayoutFrozen(false)
  • 任何正在跑的ItemAnimator不会自动停止,需要调用者去手动停止。

写了这么多,还不如直接看源码来的实在,那么就上源码:

public void setLayoutFrozen(boolean frozen) {
        if (frozen != mLayoutFrozen) {
            assertNotInLayoutOrScroll("Do not setLayoutFrozen in layout or scroll");
            if (!frozen) {
                mLayoutFrozen = false;
                if (mLayoutWasDefered && mLayout != null && mAdapter != null) {
                    requestLayout();
                }
                mLayoutWasDefered = false;
            } else {
                final long now = SystemClock.uptimeMillis();
                MotionEvent cancelEvent = MotionEvent.obtain(now, now,
                        MotionEvent.ACTION_CANCEL, 0.0f, 0.0f, 0);
                onTouchEvent(cancelEvent);
                mLayoutFrozen = true;
                mIgnoreMotionEventTillDown = true;
                stopScroll();
            }
        }
    }

首先会判断当前状态和目标状态是否一样,一样的话就直接返回了,所以我们平时在使用的时候就不需要再自己去判断了。
当然我们这里肯定是要不一样的,所以继续往下看,assertNotInLayoutOrScroll("Do not setLayoutFrozen in layout or scroll"),这句的作用是判断当前RecyclerView是不是在layout或者scroll,如果是这两种状态,那么就直接抛出异常,也就是说,不能在layout或者scroll的时候去更新adapter。

然后如果目标状态是解冻,那么就mLayoutFrozen设置为false,然后看mLayoutWasDefered是不是true,也就是有没有layout请求被延迟了,如果有就调用requestLayout(),并将mLayoutWasDefered设置为false。如果目标状态是冻住RecyclerView,那么就生成一个cancelEvent,并且传递给onTouchEvent方法,并将mLayoutFrozen设置为true,最后调用stopScroll()也就是停止RecyclerView的滑动。

setLayoutFrozen讲完了,回到setAdapter方法源码

    public void setAdapter(@Nullable Adapter adapter) {
        // bail out if layout is frozen
        setLayoutFrozen(false);
        setAdapterInternal(adapter, false, true);
        processDataSetCompletelyChanged(false);
        requestLayout();
    }

接下来是调用setAdapterInternal(adapter, false, true),源码如下

 private void setAdapterInternal(@Nullable Adapter adapter, boolean compatibleWithPrevious,
            boolean removeAndRecycleViews) {
        if (mAdapter != null) {
            mAdapter.unregisterAdapterDataObserver(mObserver);
            mAdapter.onDetachedFromRecyclerView(this);
        }
        if (!compatibleWithPrevious || removeAndRecycleViews) {
            removeAndRecycleViews();
        }
        mAdapterHelper.reset();
        final Adapter oldAdapter = mAdapter;
        mAdapter = adapter;
        if (adapter != null) {
            adapter.registerAdapterDataObserver(mObserver);
            adapter.onAttachedToRecyclerView(this);
        }
        if (mLayout != null) {
            mLayout.onAdapterChanged(oldAdapter, mAdapter);
        }
        mRecycler.onAdapterChanged(oldAdapter, mAdapter, compatibleWithPrevious);
        mState.mStructureChanged = true;
    }

主要做了以下事情:

  • 判断mAdapter是不是null,也就是旧的adapter是不是存在,如果存在则注销AdapterDataObserver,并调用onDetachedFromRecyclerView,这个方法的默认实现是空的。
  • 判断两个参数:compatibleWithPreviousremoveAndRecycleViews,这两个参数是在调用setAdapterInternal的时候传进来的,前者表示新的adapter使用的viewholder和itemtype和旧的adapter是一样的,后者表示需不需要将所有已经存在view移除,并回收。在这里compatibleWithPreviousfalseremoveAndRecycleViewstrue,所以我们需要调用removeAndRecycleViews()removeAndRecycleViews()就不讲了,很简单,如果看过前面的文章RecyclerView源码学习笔记(一)构造函数和setLayoutManager方法,就可以轻松阅读源码。
  • 调用mAdapterHelper.reset(),重置AdapterHelper,就是将还未完成的item操作,比如move,add等都删除。
  • 将旧的adapter赋值给oldAdapter,将新的adapter赋值给mAdapter,并注册AdapterDataObserver,调用onAttachedToRecyclerViewonAttachedToRecyclerView默认实现也是空的。
  • 如果LayoutManager不是null,则调用mLayout.onAdapterChanged(oldAdapter, mAdapter),这个方法他们默认实现也是空方法,且SDK中的LayoutManager的子类好像都没有重写这个方法。
  • 调用mRecycler.onAdapterChanged(oldAdapter, mAdapter, compatibleWithPrevious),源码如下
  void onAdapterChanged(Adapter oldAdapter, Adapter newAdapter,
          boolean compatibleWithPrevious) {
      clear();
      getRecycledViewPool().onAdapterChanged(oldAdapter, newAdapter, compatibleWithPrevious);
  }

先是调用clear方法,该方法做的事情是清空mAttachedScap,将mCachedViews中的view放到pool中,并清空mCachedViews。然后调用RecycledViewPool的onAdapterChanged方法,源码如下

  void onAdapterChanged(Adapter oldAdapter, Adapter newAdapter,
          boolean compatibleWithPrevious) {
      if (oldAdapter != null) {
          detach();
      }
      if (!compatibleWithPrevious && mAttachCount == 0) {
          clear();
      }
      if (newAdapter != null) {
          attach(newAdapter);
      }
  }

做了以下事情:

  • 如果oldAdapter不为null,则调用detach()方法,这个detach方法内部很简单,只是把mAttachCount减一操作,这个mAttachCount记录了当前有多少个RecyclerView和这个pool建立了关系,因为多个RecyclerView可以共用同一个pool,共用缓存的view,前提是这些view的viewtype要一样。
  • 如果compatibleWithPreviousfalsemAttachCount等于0,则清空pool。
  • 如果newAdapter不为null,则mAttachCount加一

可以看到这个方法内部还是很简单的。
回到setAdapterInternal方法,最后将mState.mStructureChanged设置为true
然后再回到setAdapter方法,接下来会调用processDataSetCompletelyChanged(false),这个方法的注释我也不看出所以然来,就直接上代码:

    void processDataSetCompletelyChanged(boolean dispatchItemsChanged) {
        mDispatchItemsChangedEvent |= dispatchItemsChanged;
        mDataSetHasChangedAfterLayout = true;
        markKnownViewsInvalid();
    }
  • mDispatchItemsChangedEventdispatchItemsChanged进行或运算,把结果赋值给mDispatchItemsChangedEvent,这个值决定在RecyclerView进行measure和layout时候要不要LayoutManager的onItemsChanged(RecyclerView)方法。
  • mDataSetHasChangedAfterLayout设置true
  • 调用markKnownViewsInvalid(),从方法注释来看是将所有view标示为invalid,还是看源码实在
     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();
    }
  • 将所有childview(包括不可见的)的viewholder添加ViewHolder.FLAG_UPDATE | ViewHolder.FLAG_INVALID标签,标示viewholder已经不可用了
  • 调用markItemDecorInsetsDirty()将所有childview(包括不可见的)的LayoutParam中的mInsetsDirty设置为true,并将mCachedView中的view的LayoutParam中的mInsetsDirty也设置为true,标示ItemDecorInset也需要更新了,而这个ItemDecorInset就是一个矩形,它的值就是我们在重写ItemDecorator的时候需要重写的getItemOffsets方法中设置的。
  • 调用mRecycler.markKnownViewsInvalid(),方法内部是将所有mCachedView中的view的viewholder添加ViewHolder.FLAG_UPDATE | ViewHolder.FLAG_INVALID标签,如果新的adapter为null,则直接将所有mCachedView中的view放到pool中,并清空mCachedView。如果有与抓取,就把与抓取的view也清空

到这里processDataSetCompletelyChanged方法的源码就看完了,接下来回到setAdapter方法,直接调用了requestLayout来进行重新布局。

setAdapter方法就看完了,主要工作还是清理工作另外就是启动重启布局计算,好了,下一篇文章就开始看布局的过程

    原文作者:杰哥哥啊
    原文地址: https://blog.csdn.net/abc512427549/article/details/79785336
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞