public class RecyclerView extends ViewGroup implements ScrollingView, NestedScrollingChild2, NestedScrollingChild3
RecyclerView实现了NestedScrollingChild2, NestedScrollingChild3接口。因为NestedScrollingChild3继承自NestedScrollingChild2,NestedScrollingChild2又继承自NestedScrollingChild,
所以我们只看NestedScrollingChild接口即可。NestedScrollingChild主要是实现了嵌套滑动的功能。
那什么是嵌套滑动呢?即:子View在处理事件的时候,通过回调让父容器也可以处理滚动
嵌套逻辑相关类:
NestedScrollView 实现 NestedScrollingParent、NestedScrollingChild
NestedScrollingParent、NestedScrollingChild
NestedScrollingParentHelper、NestedScrollingChildHelper
嵌套逻辑相关方法:
onNestedPreScroll()、onNestedScroll()
onNestedPreFling()、onNestedFling()
都是在子View中处理的,通过回调方法给的, 没有再走传统的事件分发流程
那我们看下嵌套滑动的流程:
--> RecyclerView.onTouchEvent(MotionEvent e)
// MotionEvent.ACTION_DOWN
--> startNestedScroll(nestedScrollAxis, TYPE_TOUCH);
//找到实现NestedScrollingParent的父控件
--> return getScrollingChildHelper().startNestedScroll(axes, type);
// MotionEvent.ACTION_MOVE。
--> if (dispatchNestedPreScroll(canScrollHorizontally ? dx : 0,canScrollVertically ? dy : 0, mReusableIntPair, mScrollOffset, TYPE_TOUCH)) {
--> return getScrollingChildHelper().dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow, type);
--> return dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow, TYPE_TOUCH);@NestedScrollingChildHelper
--> ViewParentCompat.onNestedPreScroll(parent, mView, dx, dy, consumed, type);
// 先让实现NestedScrollingParent的父控件的onNestedPreScroll()
--> ((NestedScrollingParent) parent).onNestedPreScroll(target, dx, dy, consumed);
dx -= mReusableIntPair[0];
dy -= mReusableIntPair[1];
mNestedOffsets[0] += mScrollOffset[0];
mNestedOffsets[1] += mScrollOffset[1];
getParent().requestDisallowInterceptTouchEvent(true);
}
// 将父控件移动的距离减去,剩下的就是自己需要滑动的距离了。
--> mLastTouchX = x - mScrollOffset[0];
mLastTouchY = y - mScrollOffset[1];
--> if (scrollByInternal(canScrollHorizontally ? dx : 0, canScrollVertically ? dy : 0, e)) {
--> scrollStep(x, y, mReusableIntPair);
// mLayout是我们设置的LayoutManager。这里以LinearLayoutManager横向滚动为例。
--> consumedX = mLayout.scrollHorizontallyBy(dx, mRecycler, mState);
--> return scrollBy(dx, recycler, state);@LinearLayoutManager
// fill方法非常关键,recyclerView的缓存和复用代码都在这里。。。
--> final int consumed = mLayoutState.mScrollingOffset + fill(recycler, mLayoutState, state, false);
//缓存
--> recycleByLayoutState(recycler, layoutState);
//复用
--> layoutChunk(recycler, state, layoutState, layoutChunkResult);
--> View view = layoutState.next(recycler);
--> final View view = recycler.getViewForPosition(mCurrentPosition);
--> return getViewForPosition(position, false);@RecyclerView
// 主要的复用代码
--> return tryGetViewHolderForPositionByDeadline(position, dryRun, FOREVER_NS).itemView;
// 测量,padding、margin、inset(分割线的空间)
--> measureChildWithMargins(view, 0, 0);
--> dispatchNestedScroll(consumedX, consumedY, unconsumedX, unconsumedY, mScrollOffset, TYPE_TOUCH, mReusableIntPair);
--> getScrollingChildHelper().dispatchNestedScroll(dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, offsetInWindow, type, consumed);
--> dispatchNestedScrollInternal(dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, offsetInWindow, type, consumed);@NestedScrollingChildHelper
--> ViewParentCompat.onNestedScroll(parent, mView, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, type, consumed);
// 调用实现NestedScrollingParent的父控件的onNestedScroll()
--> ((NestedScrollingParent) parent).onNestedScroll(target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed);
getParent().requestDisallowInterceptTouchEvent(true);
}
在上面代码中,我们追溯到了RecyclerView的复用逻辑的代码。那我们就继续深入了解下,RecyclerView是怎么进行复用的。
--> fill(recycler, mLayoutState, state, false)
--> tryGetViewHolderForPositionByDeadline(position, dryRun, FOREVER_NS).itemView;@RecyclerView
// 第①步,mChangedScrap是通过动画获取
--> holder = getChangedScrapViewForPosition(position);
//两种方式获取,通过position和stableId获取。stableId是为解决闪烁问题
--> final ViewHolder holder = mChangedScrap.get(i);
// 第②步,holder还为null,从mAttachedScrap、mCachedViews,通过position获取。
--> holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun);
--> final ViewHolder holder = mAttachedScrap.get(i);
--> final ViewHolder holder = mCachedViews.get(i);
// 第③步,holder还为null,从mAttachedScrap、mCachedViews,通过stableId获取。
--> holder = getScrapOrCachedViewForId(mAdapter.getItemId(offsetPosition), type, dryRun);
--> final ViewHolder holder = mAttachedScrap.get(i);
--> final ViewHolder holder = mCachedViews.get(i);
// 第④步,自定义复用,用起来比较麻烦,一般不用。缓存和复用都需要自己实现
--> final View view = mViewCacheExtension.getViewForPositionAndType(this, position, type);
--> holder = getChildViewHolder(view);
--> return getChildViewHolderInt(child);
--> return ((LayoutParams) child.getLayoutParams()).mViewHolder;
// 第⑤步,holder还为null,从RecycledViewPool去找
--> holder = getRecycledViewPool().getRecycledView(type);
// getRecycledViewPool() = RecycledViewPool
--> final ScrapData scrapData = mScrap.get(viewType);
--> final ArrayList scrapHeap = scrapData.mScrapHeap;
--> return scrapHeap.remove(i);
// 第⑥步,holder还为null,创建 ViewHolder 对象
--> holder = mAdapter.createViewHolder(RecyclerView.this, type);
--> final VH holder = onCreateViewHolder(parent, viewType);
// 第⑦步,去处理数据
--> bound = tryBindViewHolderByDeadline(holder, offsetPosition, position, deadlineNs);
--> mAdapter.bindViewHolder(holder, offsetPosition);
从代码分析可知,RecyclerView的复用是通过以下五种方式: mChangedScrap、mAttachedScrap、mCachedViews、mViewCacheExtension、RecycledViewPool
我们可以将其概括成四类,也就是四级缓存。
1、mChangedScrap、mAttachedScrap:用来缓存还在屏幕内的ViewHolder,局部刷新使用,主要是为了性能考虑。
2、mCachedViews:用来缓存已经移除到屏幕外的ViewHolder。
3、mViewCacheExtension:这个的创建和缓存完全由开发者自己控制,系统是没有向这个里面缓存数据的。
4、RecycledViewPool:ViewHolder的缓存池。
--> fill(recycler, mLayoutState, state, false)
--> recycleByLayoutState(recycler, layoutState);
//缓存分为向上滑动和向下滑动。看一个就够了
--> if (layoutState.mLayoutDirection == LayoutState.LAYOUT_START) {
recycleViewsFromEnd(recycler, scrollingOffset, noRecycleSpace);
} else {
recycleViewsFromStart(recycler, scrollingOffset, noRecycleSpace);
--> recycleChildren(recycler, childCount - 1, i);
//移除和回收view
--> removeAndRecycleViewAt(i, recycler);
--> recycler.recycleView(view);
// 进行mCachedViews和RecycledViewPool缓存
--> recycleViewHolderInternal(holder);@Recycler
// mViewCacheMax = DEFAULT_CACHE_SIZE = 2。可通过setViewCacheSize(int viewCount)设置
// 如果mCachedViews已经满了,则取出位置为0的view。
--> if (cachedViewSize >= mViewCacheMax && cachedViewSize > 0) {
recycleCachedViewAt(0);
// 从mCachedViews取出view,加到pool中。
--> ViewHolder viewHolder = mCachedViews.get(cachedViewIndex);
addViewHolderToRecycledViewPool(viewHolder, true);
--> getRecycledViewPool().putRecycledView(holder);
// 通过itemType去获取ViewHolder集合。
--> final int viewType = scrap.getItemViewType();
// int mMaxScrap = DEFAULT_MAX_SCRAP = 5。可通过setMaxRecycledViews(int viewType, int max)设置
final ArrayList scrapHeap = getScrapDataForType(viewType).mScrapHeap;
// 如果缓存池也满了,直接return。
if (mScrap.get(viewType).mMaxScrap <= scrapHeap.size()) {
return;
}
// 复位。将viewHolder上的数据信息清除。
scrap.resetInternal();
scrapHeap.add(scrap);
mCachedViews.remove(cachedViewIndex);
cachedViewSize--;
}
// 再将新的添加到mCachedViews中
--> mCachedViews.add(targetCacheIndex, holder);
// mCachedViews已经满了,那就存到pool中。
--> addViewHolderToRecycledViewPool(holder, true);
}
总结:
cacheView的大小默认是2,从cacheView复用viewHolder,不需要绑定数据,不需调用onBindViewHolder
缓存池的大小默认是5,从缓存池中复用viewHolder,需要重新绑定数据,需调用onBindViewHolder
如果从cacheView和缓存池中没有获取到ViewHolder,则调用onCreateViewHolder
上面我们只看到了cacheView和缓存池这两级缓存,那还有两级缓存是在哪里呢?我们来看下onMeasure和onLayout方法。
--> onMeasure(int widthSpec, int heightSpec)
--> final boolean measureSpecModeIsExactly = widthMode == MeasureSpec.EXACTLY && heightMode == MeasureSpec.EXACTLY;
// 如果设置的宽高模式是EXACTLY,那就直接返回。那具体是在哪里测量的呢?接着看onLayout方法
if (measureSpecModeIsExactly || mAdapter == null) {
return;
}
if (mState.mLayoutStep == State.STEP_START) {
dispatchLayoutStep1();
}
dispatchLayoutStep2();
--> onLayout(boolean changed, int l, int t, int r, int b)
--> dispatchLayout();
--> if (mState.mLayoutStep == State.STEP_START) {
// 执行dispatchLayoutStep1会设置mState.mLayoutStep值为State.STEP_LAYOUT
dispatchLayoutStep1();
--> mState.mLayoutStep = State.STEP_LAYOUT;
mLayout.setExactMeasureSpecsFrom(this);
// 执行dispatchLayoutStep2会设置mState.mLayoutStep值为State.STEP_ANIMATIONS
dispatchLayoutStep2();
--> mState.mLayoutStep = State.STEP_ANIMATIONS;
} else if (mAdapterHelper.hasUpdates() || mLayout.getWidth() != getWidth() || mLayout.getHeight() != getHeight()) {
mLayout.setExactMeasureSpecsFrom(this);
dispatchLayoutStep2();
} else {
mLayout.setExactMeasureSpecsFrom(this);
}
dispatchLayoutStep3();
dispatchLayoutStep1()、dispatchLayoutStep2()、dispatchLayoutStep3()都只会执行一次,dispatchLayoutStep3()只在onLayout中有调用。
--> dispatchLayoutStep1()
// 动画前布局
--> mViewInfoStore.addToPreLayout(holder, animationInfo);
//具体的测量和布局都在dispatchLayoutStep2()中
--> dispatchLayoutStep2();
--> mLayout.onLayoutChildren(mRecycler, mState);
--> detachAndScrapAttachedViews(recycler);
--> scrapOrRecycleView(recycler, i, v);
--> recycler.scrapView(view);
--> if (holder.hasAnyOfTheFlags(ViewHolder.FLAG_REMOVED | ViewHolder.FLAG_INVALID) || !holder.isUpdated() || canReuseUpdatedViewHolder(holder)) {
if (holder.isInvalid() && !holder.isRemoved() && !mAdapter.hasStableIds()) {
throw new IllegalArgumentException("Called scrap view with an invalid view."
+ " Invalid views cannot be reused from scrap, they should rebound from"
+ " recycler pool." + exceptionLabel());
}
holder.setScrapContainer(this, false);
// 缓存到mAttachedScrap中
mAttachedScrap.add(holder);
} else {
if (mChangedScrap == null) {
mChangedScrap = new ArrayList();
}
holder.setScrapContainer(this, true);
// 缓存到mChangedScrap
mChangedScrap.add(holder);
}
--> fill(recycler, mLayoutState, state, false);
--> dispatchLayoutStep3()
// 动画后布局
--> mViewInfoStore.addToPostLayout(holder, animationInfo);
总结:
mAttachedScrap、mChangedScrap缓存还在屏幕内的ViewHolder