本篇文章分析下RecyclerView的滑动机制
有几个问题关于滑动机制
Q1: RecyclerView是怎么实现滑动的?
Q2: RecyclerView是多指触摸和fling是怎么处理的?
Q3: 滑动过程中新的View是怎么填充进去的?
Q4: 滑动过程中的会进行回收吗?规则如何?
Q5: 滑动会进行预加载吗?
我们可以带着上面的问题和自己的问题看这篇文章,效率会高很多。
安卓的触摸事件
关于安卓的触摸事件,也就是那几个方法才起作用 分别是
方法 | 作用 |
---|---|
dispatchTouchEvevt | 做点击事件的分发 |
onInterceptTouchEvent | viewGroup做点击事件的拦截 |
onTouchEvevt | 处理点击事件 |
RecycleView也是一个view,所以我们从这几个方法讲起。RecycleView对于事件的触发,没有进行自己的额定制,直接继承ViewGroup。所以我们直接分析他的onInterceptTouchEvent,看他是怎么处理拦截事件的,和onTouchEvevt怎么处理点击事件的。
之后会写一篇文章从源码分析整个触摸的传递,虽然是老生常谈,但是有一些细节还需注意。
onInterceptTouchEvent
public boolean onInterceptTouchEvent(MotionEvent e) {
if (mLayoutSuppressed) {
//事件拦截模式下,不拦截任何事件和
return false;
}
if (findInterceptingOnItemTouchListener(e)) {
//处理外部的拦截事件listener
cancelScroll();
return true;
}
if (mLayout == null) {
return false;
}
。。。
final boolean canScrollHorizontally = mLayout.canScrollHorizontally();
final boolean canScrollVertically = mLayout.canScrollVertically();
switch (action) {
case MotionEvent.ACTION_DOWN:
。。。
mInitialTouchX = mLastTouchX = (int) (e.getX(actionIndex) + 0.5f);
mInitialTouchY = mLastTouchY = (int) (e.getY(actionIndex) + 0.5f);
break;
case MotionEvent.ACTION_POINTER_DOWN:
mScrollPointerId = e.getPointerId(actionIndex);
mInitialTouchX = mLastTouchX = (int) (e.getX(actionIndex) + 0.5f);
mInitialTouchY = mLastTouchY = (int) (e.getY(actionIndex) + 0.5f);
break;
case MotionEvent.ACTION_MOVE: {
final int index = e.findPointerIndex(mScrollPointerId);
final int x = (int) (e.getX(index) + 0.5f);
final int y = (int) (e.getY(index) + 0.5f);
if (mScrollState != SCROLL_STATE_DRAGGING) {
final int dx = x - mInitialTouchX;
final int dy = y - mInitialTouchY;
boolean startScroll = false;
// 判断是否应该拦截,滑动超过了临界值
if (canScrollHorizontally && Math.abs(dx) > mTouchSlop) {
mLastTouchX = x;
startScroll = true;
}
if (canScrollVertically && Math.abs(dy) > mTouchSlop) {
mLastTouchY = y;
startScroll = true;
}
if (startScroll) {
setScrollState(SCROLL_STATE_DRAGGING);
}
}
} break;
case MotionEvent.ACTION_POINTER_UP: {
onPointerUp(e);
} break;
case MotionEvent.ACTION_UP: {
mVelocityTracker.clear();
stopNestedScroll(TYPE_TOUCH);
} break;
case MotionEvent.ACTION_CANCEL: {
cancelScroll();
}
}
return mScrollState == SCROLL_STATE_DRAGGING;
}
这个方法主要判断是否拦截触摸事件。
代码中可以看到是否拦截事件是通过mScrollState判断的,如果是SCROLL_STATE_DRAGGING,就表示拦截这个事件。
mScrollState里面有几个状态比较重要,mScrollState表示RecyclerView的滑动状态:
mScrollState | 含义 |
---|---|
SCROLL_STATE_IDLE | RecyclerView 当前未滚动 |
SCROLL_STATE_DRAGGING | RecyclerView 当前正被外部输入(例如用户触摸输入)拖动。 |
SCROLL_STATE_SETTLING | RecyclerView 当前正在动画到最终位置,而不受外部控制。 |
提前拦截
根据外部状态提前处理事件,可以看到进行了3次提前的拦截
- 首先我们看到mLayoutSuppressed的使用,他是通过suppressLayout()进行控制,会阻止RecyclerView布局和滚动。具体感兴趣可以看源码。
- findInterceptingOnItemTouchListener()方法会找到外部通过addOnItemTouchListener()传入的事件处理回掉,看看他们要不要拦截这个事件。相当于外部拦截器的功能。
- 在LayoutManager为空的情况下,是不会拦截任何点击事件的。
ACTION_DOWN事件
这里主要是记录起始点的信息,包括起点的X、Y坐标。逻辑比较简单这。有一个地方可以看下,在mScrollState为SCROLL_STATE_SETTLING,也就是脱离拖动正在运动到最终位置时,如果这时我们进行触摸事件,触发DOWN事件,会调用setScrollState()方法,进而调用stopScrollersInternal()方法,这里会停止当前的滑动。这也印证了一个现象,就是在RecyclerView运动时,我们点击屏幕,会出现滑动停止的效果。
if (mScrollState == SCROLL_STATE_SETTLING) {
getParent().requestDisallowInterceptTouchEvent(true);
setScrollState(SCROLL_STATE_DRAGGING);
}
void setScrollState(int state) {
if (state == mScrollState) {
return;
}
mScrollState = state;
if (state != SCROLL_STATE_SETTLING) {
stopScrollersInternal();
}
dispatchOnScrollStateChanged(state);
}
ACTION_POINTER_DOWN 事件
这里主要处理多指点击的情况,当新手指落下时触发。会用新手指的ID刷新正在处理的手指ID,在后面的Move事件中,正是拿这个ID进行处理的滑动。所以如果放下两个手指,第一个手指的运动时不起作用的现象。这里也得到了印证。内部处理的逻辑和ACTION_DOWN事件一致。
ACTION_MOVE 事件
移动手指时触发,我们会拿生效的那个手指(最后落下的那个手指)的最新位置进行处理。这里的逻辑比较简单,主要是判断是否构成了滑动,如果能水平滚动,看水平的方向上产生的位移是否大于mTouchSlop。垂直方向也一样。如果有任一个方向生效,就设置状态为SCROLL_STATE_DRAGGING,这时就表示需要拦截事件,RecycleView来自己处理整套的事件。
onInterceptTouchEven的逻辑比较简单,我们只要知道什么情况下进行拦截就可以了。在Down事件中,是不进行拦截的,如果在Move中产生了一定的滑动距离,会进行拦截。
下面看下拦截后的onTouchEvent()是怎么实现的
onTouchEvent
public boolean onTouchEvent(MotionEvent e) {
if (mLayoutSuppressed || mIgnoreMotionEventTillDown) {
return false;
}
if (dispatchToOnItemTouchListeners(e)) {
cancelScroll();
return true;
}
if (mLayout == null) {
return false;
}
switch (action) {
case MotionEvent.ACTION_DOWN: {
。。。
} break;
case MotionEvent.ACTION_POINTER_DOWN: {
。。。
} break;
case MotionEvent.ACTION_MOVE: {
//和onInterceptTouchEvent确定SCROLL_STATE_DRAGGING状态逻辑一致
。。。
if (mScrollState == SCROLL_STATE_DRAGGING) {
// 处理嵌套滑动
if (dispatchNestedPreScroll(
canScrollHorizontally ? dx : 0,
canScrollVertically ? dy : 0,
mReusableIntPair, mScrollOffset, TYPE_TOUCH
)) {
。。。
}
mLastTouchX = x - mScrollOffset[0];
mLastTouchY = y - mScrollOffset[1];
if (scrollByInternal(
canScrollHorizontally ? dx : 0,
canScrollVertically ? dy : 0,
e)) {
getParent().requestDisallowInterceptTouchEvent(true);
}
if (mGapWorker != null && (dx != 0 || dy != 0)) {
// 预加载
mGapWorker.postFromTraversal(this, dx, dy);
}
}
} break;
case MotionEvent.ACTION_POINTER_UP: {
onPointerUp(e);
} break;
case MotionEvent.ACTION_UP: {
。。。
if (!((xvel != 0 || yvel != 0) && fling((int) xvel, (int) yvel))) {
setScrollState(SCROLL_STATE_IDLE);
}
resetScroll();
} break;
case MotionEvent.ACTION_CANCEL: {
cancelScroll();
} break;
}
return true;
}
起始也是几个方法进行提前的拦截,我们看到和onInterceptTouchEvent的判断基本一致,这里就不做分析了。 接下来就是对各个事件的分析,down事件做了初始位置的赋值,move事件查看是否构成了滑动,知识后面有点独特的逻辑。up事件里,我们看到了先算出了速度,接着执行了fling。所以我们分析move和up事件即可。
为什么onTouchEvent也需要也要判断move事件查看是否构成了滑动呢,在什么场景下会执行呢?
ACTION_DOWN事件
我们看到了dispatchNestedPreScroll方法,这个方法主要是处理嵌套滑动的。嵌套滑动也是一个比较重要的知识点,我们后面会专门讲这个。这里可以先要理解成自己产生的滑动事件,询问外部是否要先消耗一部分。
可以看到在执行完成dispatchNestedPreScroll方法后,同时也对dx和dy进行了削减,也就是表示外部已经消耗了部分滑动事件。接着会调用scrollByInternal方法,这个方法内部处理了真正的滑动事件。
boolean scrollByInternal(int x, int y, MotionEvent ev) {
consumePendingUpdateOperations();
if (mAdapter != null) {
scrollStep(x, y, mReusableIntPair);
。。。
}
if (!mItemDecorations.isEmpty()) {
//滑动过程中刷新decorations
invalidate();
}
。。。
// 处理嵌套滑动相关
return consumedNestedScroll || consumedX != 0 || consumedY != 0;
}
方法内部调用了scrollStep,并进行了嵌套滑动的处理。在滑动过程中如果有mItemDecorations,还会触发invalidate()进行刷新。接下来看下scrollStep方法。
void scrollStep(int dx, int dy, @Nullable int[] consumed) {
startInterceptRequestLayout();
onEnterLayoutOrScroll();
int consumedX = 0;
int consumedY = 0;
if (dx != 0) {
consumedX = mLayout.scrollHorizontallyBy(dx, mRecycler, mState);
}
if (dy != 0) {
consumedY = mLayout.scrollVerticallyBy(dy, mRecycler, mState);
}
}
public int scrollHorizontallyBy(int dx, RecyclerView.Recycler recycler,
RecyclerView.State state) {
if (mOrientation == VERTICAL) {
return 0;
}
return scrollBy(dx, recycler, state);
}
看到这里直接调用了,LayoutManger的scrollHorizontallyBy或者scrollVerticallyBy,这内部才是真正处理滑动事件的地方。内部会直接调用scrollBy方法。可以看出LayoutManger不光负责测量布局,还负责滚动的处理。scrollHorizontallyBy可以看出还过滤了滚动的方向,如果要让LayoutManger处理滚动,我们还需要设置滑动方向(默认是垂直)。
int scrollBy(int delta, RecyclerView.Recycler recycler, RecyclerView.State state) {
if (getChildCount() == 0 || delta == 0) {
return 0;
}
ensureLayoutState();
mLayoutState.mRecycle = true;
final int layoutDirection = delta > 0 ? LayoutState.LAYOUT_END : LayoutState.LAYOUT_START;
final int absDelta = Math.abs(delta);
updateLayoutState(layoutDirection, absDelta, true, state);
final int consumed = mLayoutState.mScrollingOffset
+ fill(recycler, mLayoutState, state, false);
final int scrolled = absDelta > consumed ? layoutDirection * consumed : delta;
mOrientationHelper.offsetChildren(-scrolled);
mLayoutState.mLastScrollDelta = scrolled;
return scrolled;
}
我们可以看到这里直接调用了fill方法,这个方法我们比较熟悉,是进行布局填充的,相信这里我们可以找到滑动或称重新填充的答案所在。
滑动事件的处理主要是通过mOrientationHelper.offsetChildren进行的。OrientationHelper封装了很多有用的工具方法,较少了很多的样板代码,我们自己开发时,也可以使用,分为垂直的和水平的。
内部会直接调用每个子View的offsetTopAndBottom方法或者offsetChildrenHorizontal方法进行位移。RecycleView是怎么滑动的我们现在应该很清晰了。
public void offsetChildrenVertical(@Px int dy) {
final int childCount = mChildHelper.getChildCount();
for (int i = 0; i < childCount; i++) {
mChildHelper.getChildAt(i).offsetTopAndBottom(dy);
}
}
下面主要看下填充的主要逻辑。
ACTION_UP事件
mVelocityTracker.addMovement(vtev);
eventAddedToVelocityTracker = true;
mVelocityTracker.computeCurrentVelocity(1000, mMaxFlingVelocity);
final float xvel = canScrollHorizontally
? -mVelocityTracker.getXVelocity(mScrollPointerId) : 0;
final float yvel = canScrollVertically
? -mVelocityTracker.getYVelocity(mScrollPointerId) : 0;
if (!((xvel != 0 || yvel != 0) && fling((int) xvel, (int) yvel))) {
setScrollState(SCROLL_STATE_IDLE);
}
这里的逻辑比较简单,计算滑动的速度,再执行fling方法,这里看到了filing,fling方法内部会调用mViewFlinger.fling()处理filing事件,内部调用逻辑比较简单,这里就不细谈。
滑动过程中的填充
可以先看下填充的主要代码,上面谈到过。在LinearLayoutManager#scrollBy
中。
final int layoutDirection = delta > 0 ? LayoutState.LAYOUT_END : LayoutState.LAYOUT_START;
final int absDelta = Math.abs(delta);
updateLayoutState(layoutDirection, absDelta, true, state);
final int consumed = mLayoutState.mScrollingOffset + fill(recycler, mLayoutState, state, false);
就这么几行,delta变量是滚动的距离。正负标志了滑动的方向,正数代表向上滑动(LayoutState.LAYOUT_END),负数代表向下滑动(LayoutState.LAYOUT_START)。可以看出layoutDirection这个参数表示要布局的方向,向上滑动布局底部,向下滑动布局顶部。然后通过调用updateLayoutState设置了一些mLayoutState里面的变量,这里包含了很多布局方面的参数。
requiredSpace:滑动的距离
final int layoutDirection = dy > 0 ? LayoutState.LAYOUT_END : LayoutState.LAYOUT_START;
private void updateLayoutState(int layoutDirection, int requiredSpace,
boolean canUseExistingSpace, RecyclerView.State state) {
mLayoutState.mInfinite = resolveIsInfinite();
mLayoutState.mExtra = getExtraLayoutSpace(state);
mLayoutState.mLayoutDirection = layoutDirection;
int scrollingOffset;
if (layoutDirection == LayoutState.LAYOUT_END) {
//向上滑动配置参数
final View child = getChildClosestToEnd();
。。。
scrollingOffset = mOrientationHelper.getDecoratedEnd(child)
- mOrientationHelper.getEndAfterPadding();
} else {
//向下滑动配置参数
final View child = getChildClosestToStart();
。。。
scrollingOffset = -mOrientationHelper.getDecoratedStart(child)
+ mOrientationHelper.getStartAfterPadding();
}
mLayoutState.mAvailable = requiredSpace;
if (canUseExistingSpace) {
mLayoutState.mAvailable -= scrollingOffset;
}
mLayoutState.mScrollingOffset = scrollingOffset;
}
这个方法内部主要是计算这次填充需要的各种参数,比如绘制的起始点和可绘制的空间等。通过上一篇的分析。可绘制空间主要是通过mLayoutState.mAvailable字段进行控制的。所以我们主要关心mLayoutState.mAvailable即可。在scrollBy的上下文下,canUseExistingSpace为true。所以mAvailable的取值为 滑动距离 - scrollingOffset。这里我们以从垂直方向,向上滑动为例,也就是填充底部的view,看看是怎么具体计算的。
final View child = getChildClosestToEnd();
int scrollingOffset = mOrientationHelper.getDecoratedEnd(child)- mOrientationHelper.getEndAfterPadding();
private View getChildClosestToStart() {
return getChildAt(mShouldReverseLayout ? getChildCount() - 1 : 0);
}
public int getDecoratedEnd(View view) {
final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams)view.getLayoutParams();
return mLayoutManager.getDecoratedBottom(view) + params.bottomMargin;
}
public int getEndAfterPadding() {
return mLayoutManager.getHeight() - mLayoutManager.getPaddingBottom();
}
- 首先通过getChildClosestToStart通过数据方向mShouldReverseLayout从ViewGroup获取最顶部的一个view。
这里的操作比较好理解,因为是向上滑动,也就是说填充底部的view,肯定要看最底部view的情况,是否应该继续填充,如果这个view还有空间没有显示出来,看没显示的空间是否小于滑动距离,如果显示的空间大就不应该填充。相反如果滑动距离大应该进行填充,所以下面的计算的逻辑我们大体也可以猜到。 - 通过getDecoratedEnd计算最底部view的bottom,再计算RecycleView的高度。两数相减这个计算正好是上面我们猜想的,判断是否还有没显示的。通过下面的计算就算出了最终的 mAvailable,可填充区域大小。
requiredSpace为滑动距离 mLayoutState.mAvailable = requiredSpace; mLayoutState.mAvailable -= scrollingOffset;
- 到fill方法中,如果可以填充的空间,就填充。 到这里我们可以解答RecycleView是在滑动中是怎么进行填充的,滑动过程中会交给LayoutManger进行执行。并且也会执行上一章讲到的第一次填充的fill方法,内部会对传入的各种配置进行填充。滑动中的配置在updateLayoutState方法里进行。如果顶部或底部的view还有没展示全的,是不会进行填充的。这样我们就回答了Q3。
滑动中的回收
显然滑动肯定会回收离开屏幕的item,那么是怎么实现的呢。我们还是看LinearLayoutManager#fill()
int fill(RecyclerView.Recycler recycler, LayoutState layoutState,
RecyclerView.State state, boolean stopOnFocusable) {
。。。
if (layoutState.mScrollingOffset != LayoutState.SCROLLING_OFFSET_NaN) {
if (layoutState.mAvailable < 0) {
layoutState.mScrollingOffset += layoutState.mAvailable;
}
recycleByLayoutState(recycler, layoutState);
}
。。。
}
主要逻辑实在fill方法内部通过调用recycleByLayoutState进行回收的。方法内部对mScrollingOffset进行了判断,不为LayoutState.SCROLLING_OFFSET_NaN则执行回收。
mScrollingOffset的赋值主要有两处
- 在首次填充的updateLayoutStateToFillEnd/updateLayoutStateToFillStart参数配置方法内
- 滚动填充的updateLayoutState参数配置犯法内
updateLayoutStateToFillEnd/updateLayoutStateToFillStart中直接赋值为LayoutState.SCROLLING_OFFSET_NaN。所以不进行回收。
updateLayout上面分析过。mScrollingOffset的值表示首末的item没有显示出来的高度。
知道他的赋值逻辑之后,看下具体的回收逻辑是如何执行的。
private void recycleByLayoutState(RecyclerView.Recycler recycler, LayoutState layoutState) {
if (!layoutState.mRecycle || layoutState.mInfinite) {
return;
}
int scrollingOffset = layoutState.mScrollingOffset;
int noRecycleSpace = layoutState.mNoRecycleSpace;
if (layoutState.mLayoutDirection == LayoutState.LAYOUT_START) {
recycleViewsFromEnd(recycler, scrollingOffset, noRecycleSpace);
} else {
recycleViewsFromStart(recycler, scrollingOffset, noRecycleSpace);
}
}
具体的回收逻辑在recycleByLayoutState中,根据布局的方向进行回收。LayoutState.LAYOUT_START情况下,要填充顶部,那么肯定要回收底部。从代码也可以看出调用了recycleViewsFromEnd。相反则调用recycleViewsFromStart。这里以向上滑动,回收顶部view调用recycleViewsFromStart举例。
private void recycleViewsFromStart(RecyclerView.Recycler recycler, int scrollingOffset,
int noRecycleSpace) {
final int limit = scrollingOffset - noRecycleSpace;
final int childCount = getChildCount();
if (mShouldReverseLayout) {
。。。
} else {
for (int i = 0; i < childCount; i++) {
View child = getChildAt(i);
//是否进行回收
if (mOrientationHelper.getDecoratedEnd(child) > limit
|| mOrientationHelper.getTransformedEndWithDecoration(child) > limit) {
recycleChildren(recycler, 0, i);
return;
}
}
}
}
是否进行回收根据主要根据两个条件,mOrientationHelper.getDecoratedEnd(child)和mOrientationHelper.getTransformedEndWithDecoration(child)和limit的比较,后者对应了矩阵变化,这里不做分析。直接看前者的判断即可。如果满足mOrientationHelper.getDecoratedEnd(child) > limit,就会进行回收,我们看下判断的左右。
-
左 mOrientationHelper.getDecoratedEnd(child) mOrientationHelper.getDecoratedEnd(child)的实现上面也说过,是拿这个View在RecyclerView在的bottom值。
-
右 limit 这里直接和limit进行比较,limit外部传入的直接是layoutState.mScrollingOffset。layoutState.mScrollingOffset的计算如下。
if (layoutState.mAvailable < 0) {
layoutState.mScrollingOffset += layoutState.mAvailable;
}
这里分了两种情况,layoutState.mAvailable是否大于0。
- 小于0,表示当前不能进行填充。所以mScrollingOffset的值在前面updateLayoutState方法中计算为
这里又出现了layoutState.mScrollingOffset += layoutState.mAvailable。所以mScrollingOffset直接变成了滑动距离。requiredSpace为滑动距离 mLayoutState.mAvailable = requiredSpace; mLayoutState.mAvailable -= scrollingOffset;
这种条件下右 limit为滑动距离。 - 大于0,这是表示可以进行填充。那么scrollingOffset就是可以填充的大小。
这种条件下右 limit为填充距离。 根据条件判断,如果从上到下,每个view的bottom如果大于limit,就应该被回收。我们根据limit的含义分别看下回收的具体逻辑。 - 大于滑动距离,如果第一个view的bottom大于滑动距离,这时候就应该回收吗,显然不是的,可以想象一下,只有这个view不可见了,才应该回收。但是按照判断,这时这个判断是true,随后就会调用recycleChildren(recycler, 0, 0);这时会直接return,不进行回收,是!确实不应该回收,但是这个逻辑就奇怪了。但是答案也比较好想,如果第二个view的bottom大于滑动距离,那么这时候就会调用recycleChildren(recycler, 0, 1),这时会回收第0个,想想这时候的情况,确实是应该回收第0个,因为他的bottom比滑动距离要下,经过滑动,肯定出了显示区域了,需要回收。
private void recycleChildren(RecyclerView.Recycler recycler, int startIndex, int endIndex) {
if (startIndex == endIndex) {
return;
}
if (endIndex > startIndex) {
for (int i = endIndex - 1; i >= startIndex; i--) {
removeAndRecycleViewAt(i, recycler);
}
} else {
for (int i = startIndex; i > endIndex; i--) {
removeAndRecycleViewAt(i, recycler);
}
}
}
- 大于填充距离,这个和上面的处理逻辑一致。不做分析了。
为什么分填充距离和滑动距离,两种呢? 其实滑动过程中,如果可以进行填充,那么填充距离和滑动距离基本是一致的。
预加载
RecycleView会预加载吗?答案是肯定会的。
是通过GapWorker进行的。在onTouchEvent()中的MOVE事件中,通过调用mGapWorker的postFromTraversal进行预加载,另一个入口在fling处理中,也就是产生滑动会判断是否需要预加载。大体思路就是通过移动的dx和dy,判断需要预加载的postion,提前进行创建。
if (mGapWorker != null && (dx != 0 || dy != 0)) {
mGapWorker.postFromTraversal(this, dx, dy);
}
跟踪发现,处理dx、dy逻辑的在collectAdjacentPrefetchPositions() 方法内。这个方法在LayoutManger里是空实现,如果我们自定义的LayoutManger需要预加载功能的话,需要自己实现这个方法。我们看下LinearLayoutManager的实现。
public void collectAdjacentPrefetchPositions(int dx, int dy, RecyclerView.State state,
LayoutPrefetchRegistry layoutPrefetchRegistry) {
int delta = (mOrientation == HORIZONTAL) ? dx : dy;
if (getChildCount() == 0 || delta == 0) {
return;
}
ensureLayoutState();
final int layoutDirection = delta > 0 ? LayoutState.LAYOUT_END : LayoutState.LAYOUT_START;
final int absDy = Math.abs(delta);
updateLayoutState(layoutDirection, absDy, true, state);
collectPrefetchPositionsForLayoutState(state, mLayoutState, layoutPrefetchRegistry);
}
void collectPrefetchPositionsForLayoutState(RecyclerView.State state, LayoutState layoutState,
LayoutPrefetchRegistry layoutPrefetchRegistry) {
final int pos = layoutState.mCurrentPosition;
if (pos >= 0 && pos < state.getItemCount()) {
layoutPrefetchRegistry.addPosition(pos, Math.max(0, layoutState.mScrollingOffset));
}
}
逻辑还算比较简单,先通过上面讲到的updateLayoutState配置LayoutState参数,再在collectPrefetchPositionsForLayoutState内部拿到layoutState要加载的mCurrentPosition。最终传入layoutPrefetchRegistry中,完成要预加载的postion的获取。
看下GapWorker在拿到需要预加载的数据是怎么进行预加载的。
private RecyclerView.ViewHolder prefetchPositionWithDeadline(RecyclerView view,
int position, long deadlineNs) {
if (isPrefetchPositionAttached(view, position)) {
return null;
}
RecyclerView.Recycler recycler = view.mRecycler;
RecyclerView.ViewHolder holder;
try {
view.onEnterLayoutOrScroll();
holder = recycler.tryGetViewHolderForPositionByDeadline(
position, false, deadlineNs);
。。。
} finally {
view.onExitLayoutOrScroll(false);
}
return holder;
}
看到了我们熟悉的tryGetViewHolderForPositionByDeadline方法,内部会通过RecycleView的四级缓存进行提取。缓存里没有那么就直接调用了onCreateView(),我们熟悉的方法。这样就提前创建了需要预加载的ViewHolder。
总结
相信通过上面的分析,我们可以回答上面的五个问题了,可以尝试回答下,不了解的可以自己阅读源码,并通过这篇文章仔细研究。
下一章我们会分析RecyclerView最重量级的缓存机制了,这也RecyclerView的核心部分。