上篇文章RecyclerView源码学习笔记(一)构造函数和setLayoutManager方法主要学习了RecyclerView初始化和setLayoutManager方法的源码,这篇我们学习setAdapter方法的源码
按照我们平时最简单的使用习惯,在调用完setLayoutManager
方法之后就要调用setAdapter方法了,直接贴源码
/**
* Set a new adapter to provide child views on demand.
*
* 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();
}
代码不多,从注释来看这个方法的作用是
上面提到的pool就是在前一篇RecyclerView源码学习笔记(一)构造函数和setLayoutManager方法中说的RecycledViewPool。
接下来一行一行看下去。第一行是调用了setLayoutFrozen(false)
,这个方法的作用是什么呢?从注释来看可以将此方法归纳为以下几点:
true
,相当于这个RecyclerView被冻住了,那么layout的请求将被推迟,直到调用setLayoutFrozen(false)
smoothScrollBy
,scrollBy
,scrollToPosition
,smoothScrollToPosition
这些方法的调用将直接被丢弃,也就是直接返回,这个我们可以看一下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);
}
}
onFocusSearchFailed
不会被调用,但是LayoutManager的scrollToPosition
和smoothScrollToPosition
并不受影响,还可以照常运行。 setAdapter
方法和swapAdapter
方法会自动结束冰冻状态,就是会调用setLayoutFrozen(false)
。写了这么多,还不如直接看源码来的实在,那么就上源码:
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;
}
主要做了以下事情:
mAdapte
r是不是null
,也就是旧的adapter是不是存在,如果存在则注销AdapterDataObserver,并调用onDetachedFromRecyclerView
,这个方法的默认实现是空的。compatibleWithPrevious
和removeAndRecycleViews
,这两个参数是在调用setAdapterInternal
的时候传进来的,前者表示新的adapter使用的viewholder和itemtype和旧的adapter是一样的,后者表示需不需要将所有已经存在view移除,并回收。在这里compatibleWithPrevious
是false
,removeAndRecycleViews
是true
,所以我们需要调用removeAndRecycleViews()
,removeAndRecycleViews()
就不讲了,很简单,如果看过前面的文章RecyclerView源码学习笔记(一)构造函数和setLayoutManager方法,就可以轻松阅读源码。mAdapterHelper.reset()
,重置AdapterHelper,就是将还未完成的item操作,比如move,add等都删除。mAdapter
,并注册AdapterDataObserver,调用onAttachedToRecyclerView
,onAttachedToRecyclerView
默认实现也是空的。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要一样。compatibleWithPrevious
为false
且mAttachCount
等于0,则清空pool。newAdapter
不为null
,则mAttachCount
加一可以看到这个方法内部还是很简单的。
回到setAdapterInternal
方法,最后将mState.mStructureChanged
设置为true
。
然后再回到setAdapter
方法,接下来会调用processDataSetCompletelyChanged(false)
,这个方法的注释我也不看出所以然来,就直接上代码:
void processDataSetCompletelyChanged(boolean dispatchItemsChanged) {
mDispatchItemsChangedEvent |= dispatchItemsChanged;
mDataSetHasChangedAfterLayout = true;
markKnownViewsInvalid();
}
mDispatchItemsChangedEvent
和dispatchItemsChanged
进行或运算,把结果赋值给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();
}
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方法就看完了,主要工作还是清理工作另外就是启动重启布局计算,好了,下一篇文章就开始看布局的过程