本文紧接着上一篇文章来分析RecyclerView的滚动,从中咱们能够窥探到RecyclerView缓存的回收与再利用。java
分析源码,不能太盲目,不然目标过大容易迷失本身,所以我仍是选取上篇文章的例子所示用的代码,做为分析的目标缓存
mRecyclerView.setLayoutManager(new LinearLayoutManager(this));
mRecyclerView.setAdapter(new RvAdapter());
复制代码
为了分析RecyclerView滚动,如今假设手指从下往上滑动,后面将以此为根基进行分析。app
- 与前面文章约定的习惯一致,我将使用RV表示RecyclerView,用LM表示LayoutManager,用LLM表示LinearLayoutManager。
- 阅读本文须要提早了解RecyclerView源码剖析: 基本显示。
根据事件分发的原理可知,RV的滚动由onTouchEvent()
完成,精简代码以下布局
public boolean onTouchEvent(MotionEvent e) {
// 经过LayoutManager判断是否能够水平或者垂直滚动
final boolean canScrollHorizontally = mLayout.canScrollHorizontally();
final boolean canScrollVertically = mLayout.canScrollVertically();
switch (action) {
case MotionEvent.ACTION_MOVE: {
int dx = mLastTouchX - x;
// 1. 获取手指移动的距离
// dy大于0,表明手指向上滑动
int dy = mLastTouchY - y;
// ... 省略判断是否能执行滚动的代码
if (mScrollState == SCROLL_STATE_DRAGGING) {
// ...省略nested scroll的代码
// 2. 执行滚动
if (scrollByInternal(
canScrollHorizontally ? dx : 0,
canScrollVertically ? dy : 0,
e)) {
// 成功完成一次滚动,就请求父View再也不截断后续事件
getParent().requestDisallowInterceptTouchEvent(true);
}
}
} break;
}
return true;
}
复制代码
例子中使用的LLM支持的是垂直滚动,而且手指从下往上滑动,所以dx
值为0,dy
值大于0(注意,不是小于0)。post
计算完滑动距离后,调用scrollByInternal()
来完成滚动。动画
这里省略了事件处理的代码,你们能够自行分析。ui
boolean scrollByInternal(int x, int y, MotionEvent ev) {
int consumedX = 0;
int consumedY = 0;
if (mAdapter != null) {
mReusableIntPair[0] = 0;
mReusableIntPair[1] = 0;
// 执行滚动,并把滚动消耗的距离放到第二个参数中。
scrollStep(x, y, mReusableIntPair);
// 计算滚动消耗的距离
consumedX = mReusableIntPair[0];
consumedY = mReusableIntPair[1];
// ...
}
// 若是有ItemDecoration当即刷新
if (!mItemDecorations.isEmpty()) {
invalidate();
}
// ...省略nested scroll和over scroll的代码
// 通知监听RV滑动的监听者
if (consumedX != 0 || consumedY != 0) {
dispatchOnScrolled(consumedX, consumedY);
}
// 若是没有ItemDecoration,也须要刷新
if (!awakenScrollBars()) {
invalidate();
}
// 只要有滑动,就返回true
return consumedNestedScroll || consumedX != 0 || consumedY != 0;
}
复制代码
这里的逻辑很清楚,执行滚动,通知监听器,刷新界面。所以咱们把目光集中在scrollStep()
便可this
void scrollStep(int dx, int dy, @Nullable int[] consumed) {
// ...
int consumedX = 0;
int consumedY = 0;
// 滚动交给LayoutManager执行
if (dx != 0) {
consumedX = mLayout.scrollHorizontallyBy(dx, mRecycler, mState);
}
if (dy != 0) {
consumedY = mLayout.scrollVerticallyBy(dy, mRecycler, mState);
}
// ...
// 保存滚动的距离到第二个参数中
if (consumed != null) {
consumed[0] = consumedX;
consumed[1] = consumedY;
}
}
复制代码
原来RV把滚动的逻辑交给了LM,例子中使用的是LLM,并且支持的是垂直滚动,所以咱们来分析LLM的scrollVerticallyBy()
方法。spa
public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler, RecyclerView.State state) {
if (mOrientation == HORIZONTAL) {
return 0;
}
return scrollBy(dy, recycler, state);
}
复制代码
LLM是经过scrollBy()
实现滚动的,因为代码逻辑跨度比较大,所以我将分布讲解。code
int scrollBy(int delta, RecyclerView.Recycler recycler, RecyclerView.State state) {
if (getChildCount() == 0 || delta == 0) {
return 0;
}
ensureLayoutState();
// 表示子View可被回收
mLayoutState.mRecycle = true;
// 手指从下往上滑动,delta大于0,取值LayoutState.LAYOUT_END
final int layoutDirection = delta > 0 ? LayoutState.LAYOUT_END : LayoutState.LAYOUT_START;
final int absDelta = Math.abs(delta);
// 1. 更新mLayoutState信息
updateLayoutState(layoutDirection, absDelta, true, state);
// ...
return scrolled;
}
复制代码
根据前面文章可知,updateLayoutState()
为子View布局更新mLayouState
信息
private void updateLayoutState(int layoutDirection, int requiredSpace, boolean canUseExistingSpace, RecyclerView.State state) {
// 大部分状况都为false
mLayoutState.mInfinite = resolveIsInfinite();
// 保存布局的方向
mLayoutState.mLayoutDirection = layoutDirection;
mReusableIntPair[0] = 0;
mReusableIntPair[1] = 0;
// 计算额外的布局空间,若是不是smooth scroll,通常为0
calculateExtraLayoutSpace(state, mReusableIntPair);
int extraForStart = Math.max(0, mReusableIntPair[0]);
int extraForEnd = Math.max(0, mReusableIntPair[1]);
// 根据例子分析,值为true
boolean layoutToEnd = layoutDirection == LayoutState.LAYOUT_END;
// 表明布局可用的额外空间
mLayoutState.mExtraFillSpace = layoutToEnd ? extraForEnd : extraForStart;
// 表明不须要回收的空间
mLayoutState.mNoRecycleSpace = layoutToEnd ? extraForStart : extraForEnd;
int scrollingOffset;
if (layoutToEnd) {
// 增长RV底部的padding
mLayoutState.mExtraFillSpace += mOrientationHelper.getEndPadding();
// 获取最底部的一个子View
final View child = getChildClosestToEnd();
// Adapter数据遍历方向,这里取ITEM_DIRECTION_TAIL,表示从前日后遍历
mLayoutState.mItemDirection = mShouldReverseLayout ? LayoutState.ITEM_DIRECTION_HEAD
: LayoutState.ITEM_DIRECTION_TAIL;
// 计算获取的下一个View,在Adapter中的position
mLayoutState.mCurrentPosition = getPosition(child) + mLayoutState.mItemDirection;
// 计算布局的起始坐标
mLayoutState.mOffset = mOrientationHelper.getDecoratedEnd(child);
// 计算在不一样添加子View的状况下,RV能够滚动的距离
scrollingOffset = mOrientationHelper.getDecoratedEnd(child)
- mOrientationHelper.getEndAfterPadding();
} else {
}
// requiredSpace值为dy
// mLayoutState.mAvailable表明布局的可用空间
mLayoutState.mAvailable = requiredSpace;
// 此时分析的状况canUseExistingSpace为true
if (canUseExistingSpace) {
// 我的认为,这段代码放在这里是无心义的
mLayoutState.mAvailable -= scrollingOffset;
}
mLayoutState.mScrollingOffset = scrollingOffset;
}
复制代码
这里有不少变量是在前面文章中介绍过的,可是重点要关注scrollingOffset
变量,用一副图解释下
由图可知,滑动距离还没达到scrollingOffset
时,RV是不须要填充子View的。
更新完信息后,接下来就要决定这次的滑动是否须要回收不可见子View,以及是否须要填充新View。
int scrollBy(int delta, RecyclerView.Recycler recycler, RecyclerView.State state) {
// ...
// 1. 更新mLayoutState信息
updateLayoutState(layoutDirection, absDelta, true, state);
// 2. 根据状况回收,建立子View,
int added = fill(recycler, mLayoutState, state, false);
// ...
return scrolled;
}
复制代码
fill()
方法的命名很差,它不只仅完成填充子View的任务,还完成了子View的回收任务
int fill(RecyclerView.Recycler recycler, LayoutState layoutState, RecyclerView.State state, boolean stopOnFocusable) {
final int start = layoutState.mAvailable;
// 1. 在添加子View前,回收那些预计不可见子View
if (layoutState.mScrollingOffset != LayoutState.SCROLLING_OFFSET_NaN) {
// 我的以为,将刚才更新mLayout时,计算layoutState.mAvailable的代码移动到这里比较恰当
// TODO ugly bug fix. should not happen
if (layoutState.mAvailable < 0) {
layoutState.mScrollingOffset += layoutState.mAvailable;
}
recycleByLayoutState(recycler, layoutState);
}
// 可用空间仍是要包括计算出来的额外空间
int remainingSpace = layoutState.mAvailable + layoutState.mExtraFillSpace;
// 用来保存布局的结果
LayoutChunkResult layoutChunkResult = mLayoutChunkResult;
// 2. 若是有可用空间,而且还有子View能够填充,那么就填充子View,并计算是否会输不可见子View
while ((layoutState.mInfinite || remainingSpace > 0) && layoutState.hasMore(state)) {
layoutChunkResult.resetInternal();
// 2.1 添加子View
layoutChunk(recycler, state, layoutState, layoutChunkResult);
// 这里处理的是全部子View添加完毕的状况
if (layoutChunkResult.mFinished) {
break;
}
// 计算下次布局的坐标偏移量
layoutState.mOffset += layoutChunkResult.mConsumed * layoutState.mLayoutDirection;
// 添加子View后,再次计算可用空间
if (!layoutChunkResult.mIgnoreConsumed || layoutState.mScrapList != null
|| !state.isPreLayout()) {
layoutState.mAvailable -= layoutChunkResult.mConsumed;
// we keep a separate remaining space because mAvailable is important for recycling
remainingSpace -= layoutChunkResult.mConsumed;
}
// 2.2 添加子View后,在执行滚动前,回收那些预计不可见的子View
if (layoutState.mScrollingOffset != LayoutState.SCROLLING_OFFSET_NaN) {
layoutState.mScrollingOffset += layoutChunkResult.mConsumed;
if (layoutState.mAvailable < 0) {
layoutState.mScrollingOffset += layoutState.mAvailable;
}
recycleByLayoutState(recycler, layoutState);
}
}
// 表示添加子View使用了多少空间,若是没有添加子View,值就是0
return start - layoutState.mAvailable;
}
复制代码
这段代码把View的回收以及建立,混合在一块儿。好在前面的文章已经分析了填充子View的过程,那么接下来我挑一种情形来分析子View的回收过程。这个情形以下图
如图所示,dy
表明手指向上滑动的距离差,很明显dy
是小于scrollingOffset
(不添加子View能够滚动的距离),可是却大于第一个子View的底部距离。
在这种状况下,咱们彻底能够预计,即将到来的滚动,确定会让第一个子View不可见,所以咱们能够提早回收这个子View。
我把须要的代码结合在一块儿来分析提早回收子View的过程,代码以下
private void updateLayoutState(int layoutDirection, int requiredSpace, boolean canUseExistingSpace, RecyclerView.State state) {
// requiredSpace的值就是dy
mLayoutState.mAvailable = requiredSpace;
// canUseExistingSpace为true
if (canUseExistingSpace) {
// dy小于scrollingOffset时,mLayoutState.mAvailable为负值
mLayoutState.mAvailable -= scrollingOffset;
}
}
int fill(RecyclerView.Recycler recycler, LayoutState layoutState, RecyclerView.State state, boolean stopOnFocusable) {
// 在添加子View前,回收那些预计不可见子View
if (layoutState.mScrollingOffset != LayoutState.SCROLLING_OFFSET_NaN) {
// 处理负值的状况
if (layoutState.mAvailable < 0) {
// layoutState.mScrollingOffset从新计算后值为dy
layoutState.mScrollingOffset += layoutState.mAvailable;
}
recycleByLayoutState(recycler, layoutState);
}
// ...
}
复制代码
根据前面图片展现的状况,dy
是小于mScrollingOffset
的,代码中最终计算计算出的layoutState.mScrollingOffset
的值就为dy
,后面的代码将会比较顶部的子View在滚动dy
的状况下,是否不可见,看下recycleByLayoutState()
如何实现的
private void recycleByLayoutState(RecyclerView.Recycler recycler, LayoutState layoutState) {
// 此时值为dy
int scrollingOffset = layoutState.mScrollingOffset;
// 不是smooth scroll,值为0
int noRecycleSpace = layoutState.mNoRecycleSpace;
if (layoutState.mLayoutDirection == LayoutState.LAYOUT_START) {
} else {
recycleViewsFromStart(recycler, scrollingOffset, noRecycleSpace);
}
}
private void recycleViewsFromStart(RecyclerView.Recycler recycler, int scrollingOffset, int noRecycleSpace) {
// 此时值为dy
final int limit = scrollingOffset - noRecycleSpace;
final int childCount = getChildCount();
if (mShouldReverseLayout) {
} else {
for (int i = 0; i < childCount; i++) {
View child = getChildAt(i);
// 遍历获取第一个底部坐标大于dy的子View
if (mOrientationHelper.getDecoratedEnd(child) > limit
|| mOrientationHelper.getTransformedEndWithDecoration(child) > limit) {
// 若是找到这个子View,就删除前面的全部子View,由于它们都不可见
recycleChildren(recycler, 0, i);
return;
}
}
}
}
复制代码
根据前面的状况来分析这段代码,能够计算出limit
的值就是dy
,所以经过遍历获取的第一个底部坐标大于limit
值的子View,就是前面图片上的第二个子View,所以调用recycleChildren(recycler, 0, i)
,回收第二个子View前面的全部子View,也就是回收第一个子View,由于它立刻不可见。
recycleChildren()
经过removeAndRecycleViewAt()
方法逐个回收子View
public void removeAndRecycleViewAt(int index, @NonNull Recycler recycler) {
// 获取子View
final View view = getChildAt(index);
// 1. 从RV中移除子View
removeViewAt(index);
// 2. 使用Recycler回收子View
recycler.recycleView(view);
}
复制代码
回收分为两步,首先从RV中移除子View,这是一个ViewGroup
移除子View的操做。而后利用RecyclerView.Recycler
进行回收子View。
如今咱们把目光聚焦到Recycler
是如何回收子View的!!!
此时此刻真是使人激动的时候,由于咱们能够开始窥探Recycler的缓存。
public void recycleView(@NonNull View view) {
// 经过布局参数获取ViewHolder
ViewHolder holder = getChildViewHolderInt(view);
// ...
// 回收
recycleViewHolderInternal(holder);
}
复制代码
首先获取了View的ViewHolder
,而后调用recycleViewHolderInternal()
来回收ViewHolder
。
回收的是
ViewHolder
而不是View,有意思!
void recycleViewHolderInternal(ViewHolder holder) {
// ... 省略状态判断的代码
// ViewHolder没有设置FLAG_NOT_RECYCLABLE,而且View处于transient state时,这个变量值为true
final boolean transientStatePreventsRecycling = holder
.doesTransientStatePreventRecycling();
// Adapter#onFailedToRecycleView()作清理工做后,表明可强制刷新
final boolean forceRecycle = mAdapter != null
&& transientStatePreventsRecycling
&& mAdapter.onFailedToRecycleView(holder);
boolean cached = false;
boolean recycled = false;
if (forceRecycle || holder.isRecyclable()) {
// 1. 缓存最大空间大于0而且ViewHolder状态正常,就把它添加到缓存中
// mViewCacheMax表明缓存空间大小,默认为2
if (mViewCacheMax > 0
&& !holder.hasAnyOfTheFlags(ViewHolder.FLAG_INVALID
| ViewHolder.FLAG_REMOVED
| ViewHolder.FLAG_UPDATE
| ViewHolder.FLAG_ADAPTER_POSITION_UNKNOWN)) {
int cachedViewSize = mCachedViews.size();
// 若是缓存空间已满,就清理空间
if (cachedViewSize >= mViewCacheMax && cachedViewSize > 0) {
// 移除第一个缓存的ViewHolder,并使用Recycler进行回收
recycleCachedViewAt(0);
cachedViewSize--;
}
int targetCacheIndex = cachedViewSize;
// ... 省略预获取信息的操做
// 缓存空间足够,就缓存ViewHolder
mCachedViews.add(targetCacheIndex, holder);
cached = true;
}
// 2. 没法使用缓存,就使用Recycler回收ViewHolder
if (!cached) {
addViewHolderToRecycledViewPool(holder, true);
recycled = true;
}
} else {
}
// ...
}
复制代码
咱们来分析下这里使用的缓存回收策略
ArrayList mCachedViews
最大空间大于0,而且ViewHolder
状态正常(没设置过FLAG_INVALID
, FLAG_REMOVED
, FLAG_UPDATE
, FLAG_ADAPTER_POSITION_UNKNOWN
)。
Recycler
回收。ViewHolder
状态不正常,那么就用RecyclerView
进行回收。这里用到两个缓存,有什么区别呢,后面揭晓。
如今来看下addViewHolderToRecycledViewPool()
如何回收ViewHolder
的
void addViewHolderToRecycledViewPool(@NonNull ViewHolder holder, boolean dispatchRecycled) {
clearNestedRecyclerViewIfNotNested(holder);
// ...省略Accessbility
// 通知监听者ViewHolder被回收
if (dispatchRecycled) {
dispatchViewRecycled(holder);
}
// 被回收前,设置ViewHolder的mOwnerRecyclerView为null
holder.mOwnerRecyclerView = null;
// 使用RecylerPool回收ViewHolder
getRecycledViewPool().putRecycledView(holder);
}
复制代码
原来使用RecyclerPool
进行回收的
public void putRecycledView(ViewHolder scrap) {
final int viewType = scrap.getItemViewType();
// 根据类型,获取回收池中的集合
final ArrayList<ViewHolder> scrapHeap = getScrapDataForType(viewType).mScrapHeap;
// 若是集合已满,就不进行这次回收(mMaxScrap默认为5)
if (mScrap.get(viewType).mMaxScrap <= scrapHeap.size()) {
return;
}
scrap.resetInternal();
// 添加须要回收的ViewHolder
scrapHeap.add(scrap);
}
复制代码
咱们都直到回收池是为了节省内存而生,可是它也不能盲目的无限回收,RecyclerPool
的回收上限是5。若是达到上限,那么提交给回收池的ViewHolder
就会被忽略掉。
若是没有达到回收池的上限,首先根据须要被回收的ViewHolder
的类型获取一个集合,而后把须要回收的ViewHolder
放到这个集合中。
如今咱们已经了解了Recycler
回收子View的方式,那么如今咱们来看看如何再利用回收的ViewHolder。
根据前面文章的分析可知,获取子View是在layoutChunk()
中发生的
void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state, LayoutState layoutState, LayoutChunkResult result) {
// 1. 获取子View
View view = layoutState.next(recycler);
// 2. 添加子View
// 3. 测量子View
// 4. 布局子View
// ...
}
复制代码
获取子View是从Recycler
中获取的
ViewHolder tryGetViewHolderForPositionByDeadline(int position, boolean dryRun, long deadlineNs) {
// ...
ViewHolder holder = null;
// 1. 从mAttachedScrap,hidden(正在消失的View), mCachedViews中获取
if (holder == null) {
holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun);
if (holder != null) {
// 对于不是pre-layout过程,这里主要就是检查ViewHolder类型是否匹配
if (!validateViewHolderForOffsetPosition(holder)) {
} else {
romScrapOrHiddenOrCache = true;
}
}
}
if (holder == null) {
final int offsetPosition = mAdapterHelper.findPositionOffset(position);
final int type = mAdapter.getItemViewType(offsetPosition);
// 2. 若是Adapter支持stable ids,就根据stable id从mAttachedScrap, mCachedViews中获取
if (mAdapter.hasStableIds()) {
}
// 3. 从自定义的缓存mViewCacheExtension中获取
if (holder == null && mViewCacheExtension != null) {
}
if (holder == null) { // fallback to pool
// 4. 从RecylerPool中获取
holder = getRecycledViewPool().getRecycledView(type);
if (holder != null) {
holder.resetInternal();
if (FORCE_INVALIDATE_DISPLAY_LIST) {
invalidateDisplayListInt(holder);
}
}
}
// 5. 若是都获取不到,只能调用Adapter建立
if (holder == null) {
holder = mAdapter.createViewHolder(RecyclerView.this, type);
}
}
// ...
boolean bound = false;
if (mState.isPreLayout() && holder.isBound()) {
}
// 6. 根据状况决定是否从新绑定
else if (!holder.isBound() || holder.needsUpdate() || holder.isInvalid()) {
final int offsetPosition = mAdapterHelper.findPositionOffset(position);
bound = tryBindViewHolderByDeadline(holder, offsetPosition, position, deadlineNs);
}
// 7. 更新布局参数
// ....
}
复制代码
第二步,涉及到Adapter
的stable id
功能,大部分状况下用不到,因此暂时不考虑。
第三步使用的是自定义缓存,目前不分析,你们能够本身去研究下自定义缓存如何使用。
第一步,从mAttachedScrap
,hidden view
(正在消失的View),mCachedViews
中获取ViewHolder
。其中与咱们相关的就是缓存mCachedViews
,代码以下
ViewHolder getScrapOrHiddenOrCachedHolderForPosition(int position, boolean dryRun) {
final int scrapCount = mAttachedScrap.size();
// 1. 从mAttachedScrap获取
// 2. 从hidden views中获取
// 3. 从mCachedViews中获取
final int cacheSize = mCachedViews.size();
for (int i = 0; i < cacheSize; i++) {
final ViewHolder holder = mCachedViews.get(i);
// 获取ViewHolder的条件
// 1. ViewHolder没设置过FLAG_INVALID
// 2. ViewHolder的布局位置要和添加的位置同样
// 3. 没有被过渡动画所使用
if (!holder.isInvalid() && holder.getLayoutPosition() == position
&& !holder.isAttachedToTransitionOverlay()) {
// 本次分析中dryRun为false,表明获取ViewHolder以前,须要从缓存中移除
if (!dryRun) {
mCachedViews.remove(i);
}
return holder;
}
}
return null;
}
复制代码
咱们发现从mCachedViews
获取ViewHolder
仍是有条件的
第一个和第三个条件,对于此时的分析是知足的。第二个条件很显然不必定知足,它是处理这样的状况,假如手指向上滑动致使第一个View被回收,此时手指立刻向下滑动,这个时候就须要让刚被回收的第一个View再显示出来,所以就能够再利用刚回收的ViewHolder
。
从这里咱们能够明白,mCachedViews
是用来处理刚刚被回收的子View,而后又要让这个子View再显示的状况。
从前面回收ViewHolder
的分析可知,回收还用到了RecyclerPool
,所以这里咱们还要分析从RecyclerPool
获取ViewHolder
的状况,也就是第四步,它调用的是RecyclerPool#getRecyclerView()
方法
public ViewHolder getRecycledView(int viewType) {
// 根据类型获取缓存的集合
final ScrapData scrapData = mScrap.get(viewType);
if (scrapData != null && !scrapData.mScrapHeap.isEmpty()) {
final ArrayList<ViewHolder> scrapHeap = scrapData.mScrapHeap;
for (int i = scrapHeap.size() - 1; i >= 0; i--) {
// ViewHolder没有被过渡动画使用,那么默认返回的就是第一个ViewHolder
if (!scrapHeap.get(i).isAttachedToTransitionOverlay()) {
return scrapHeap.remove(i);
}
}
}
return null;
}
复制代码
这里与前面分析RecyclerPool
的缓存原理一致,首先根据类型获取缓存集合,而后返回一个没有被过渡动画使用的ViewHolder
。可是这个ViewHolder
中保存的数据是不可靠的,所以须要从新绑定一次,也就是要调用Adapter#bindViewHolder()
方法,从而会调用子类Adapter
的onBindViewHolder()
方法。
咱们来总结下mCachedViews
和RecyclerPool
两个缓存的的特色
mCachedViews
适用于子View刚被回收,而后又立刻要显示的状况。从这个缓存中获取的ViewHolder
不须要再绑定。RecyclerPool
是为了节约内存。从这个缓存中获取的ViewHolder
是须要从新绑定的。那么若是缓存中获取不到ViewHolder
呢?天然须要经过Adapter
建立了,这个过程在前面的文章已经分析过了。
如今,咱们已经明白了子View如何回收再利用,那么如今咱们来进行最后一步,分析RV如何实现滚动
可能你已经忘记从哪里分析了,不要紧,已经分析到了LLM的scrollBy()
中的第三步
int scrollBy(int delta, RecyclerView.Recycler recycler, RecyclerView.State state) {
// ...
// 1. 更新mLayoutState信息
updateLayoutState(layoutDirection, absDelta, true, state);
// 2. 根据状况回收,建立子View,
int added = fill(recycler, mLayoutState, state, false);
// 3. 计算实际须要滚动的距离
final int consumed = mLayoutState.mScrollingOffset + added;
if (consumed < 0) {
return 0;
}
final int scrolled = absDelta > consumed ? layoutDirection * consumed : delta;
// 4. 执行RV的滚动
mOrientationHelper.offsetChildren(-scrolled);
return scrolled;
}
复制代码
第三步,计算实际须要滚动的距离。这里分多种状况,请你们自行分析。
第四步,实现RV的滚动,这里是经过基类LayoutManager#offsetChildrenVertical()
,再由RecyclerView#offsetChildrenVertical()
实现的
public void offsetChildrenVertical(@Px int dy) {
final int childCount = mChildHelper.getChildCount();
for (int i = 0; i < childCount; i++) {
mChildHelper.getChildAt(i).offsetTopAndBottom(dy);
}
}
复制代码
原来是经过位移RV的每个子View完成来完成RV的总体滚动。
RecyclerView
每个过程的分析,都须要极大的耐心和毅力,为写这篇文章,我酝酿了每个细节,只但愿能讲解明白。可是限于篇幅,文章中不少小细节只给出了注释,须要你们在源码中本身去分析去体会,才能真正作到融会贯通。
下一篇文章,我将讲述ItemDecoration
的原理,以及实现一个很是有意思的案例,效果以下
不过,这个准备过程可能有点长,喜欢我文章的朋友能够点个赞,关注一波。