前语
上章咱们讲了右半部分,本章咱们讲解左半部分;
怎样复用原理
咱们在滑动的时分,才会触发 RecyclerView 的收回复用,所以咱们从 RecyclerView 的 onTouchEvent 办法下手;咱们来看下滑动的时分,是怎样和 LayoutManager 关联起来的;
咱们进入 onTouchEvent 的 ACTION_MOVE 看下:
public boolean onTouchEvent(MotionEvent e) {
//
...
// 省掉部分代码
case MotionEvent.ACTION_MOVE:
if(scrollByInternal(xxxx)){}
break;
}
咱们进入scrollByInternal( canScrollHorizontally ? dx : 0, canScrollVertically ? dy : 0, e) 这个办法看下:
boolean scrollByInternal(int x, int y, MotionEvent ev) {
//
...
// 省掉部分代码
scrollStep(x, y, mResuableIntPair);
}
咱们进入这个 scrollStep 办法看下:
根据滑动方向,别离调用了 LayoutManager 不同的办法,咱们挑选其间一个进入看下:
咱们挑选 LinearLayoutManager 的 scrollVerticalcallBy 办法看下:
public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler,
RecyclerView.State state) {
if (mOrientation == HORIZONTAL) {
return 0;
}
return scrollBy(dy, recycler, state);
}
这儿直接调用了 scrollBy 办法,咱们进入这个办法看一下:
咱们进入这个 fill 办法看下:
可以看到,咱们在一个 while 循环中多次调用 layoutChunk 办法,这个 layoutChunk 办法便是获取 view 填充咱们的 RecyclerView 的,咱们进入这个办法看下:
从缓存中获取 View 并添加到 RecyclerView 中,咱们进入这个 next 办法看下:
View next(RecyclerView.Recycler recycler) {
if (mScrapList != null) {
return nextViewFromScrapList();
}
final View view = recycler.getViewForPosition(mCurrentPosition);
mCurrentPosition += mItemDirection;
return view;
}
从 Recycler 中根据方位获取一个 View,咱们进入这个 getViewForPosition 看下:
View getViewForPosition(int position, boolean dryRun) {
return tryGetViewHolderForPositionByDeadline(position, dryRun, FOREVER_NS).itemView;
}
拿到 ViewHolder 之后,直接获取它的 itemView 并返回,一切的将 ViewHolder 从缓存取出来复用的逻辑都在这儿,咱们来看下 ViewHolder 是怎样复用的:
这儿总共包含了四级缓存,对应着四级复用:
-
mChangeScrp 和 mAttachedScrp;用来缓存还在屏幕内的 ViewHolder
-
mCachedViews;用来缓存移除屏幕外地 ViewHolder
-
mViewCacheExtension;开发给用户的自定义扩展缓存,需要用户自己办理 View 的创立和缓存
-
RecyclerViewPool;ViewHolder 缓存池
第一次缓存复用
// 0) If there is a changed scrap, try to find from there
if (mState.isPreLayout()) {
holder = getChangedScrapViewForPosition(position);
fromScrapOrHiddenOrCache = holder != null;
}
与动画相关的,经过方位从 mChangeScrp 中获取;
第2次缓存复用
// 1) Find by position from scrap/hidden list/cache
if (holder == null) {
holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun);
}
针对方位的,经过方位从 mAttachedScrap 和 mCachedViews 中获取;
holder = getScrapOrCachedViewForId(mAdapter.getItemId(offsetPosition),
type, dryRun);
仍是针对mAttachedScrap 和 mCachedViews 中获取的,经过 ViewType 和 ItemId来区别,所以这个也属于第2次缓存复用;
第三次缓存复用
if (holder == null && mViewCacheExtension != null) {
// We are NOT sending the offsetPosition because LayoutManager does not
// know it.
final View view = mViewCacheExtension
.getViewForPositionAndType(this, position, type);
if (view != null) {
holder = getChildViewHolder(view);
}
// 省掉部分代码
...
//
}
开发给开发中的自定义扩展缓存,需要开发者自己办理 View 的创立和缓存,一般用不到;
第四次缓存复用
holder = getRecycledViewPool().getRecycledView(type);
从缓存池中获取;
复用的流程咱们已经整理通了,那么拿到复用的 ItemView 之后,又是怎样调用到 onBindViewHolder 以及怎样调用的 onCreateViewHolder 呢?咱们继续分析:
假如四级缓存中都没有可以复用的 ViewHolder 的话,那么就需要进行 ViewHolder 的创立流程了;
if (holder == null) {
holder = mAdapter.createViewHolder(RecyclerView.this, type);
}
创立之后,便是进行 ViewHolder 的绑定流程了;
bound = tryBindViewHolderByDeadline(holder, offsetPosition, position, deadlineNs);
终究就会调用到 onBindViewHolder 办法;
全体时序图如下:
怎样缓存原理
缓存发生了 RecyclerView 的 onLayout 办法中,咱们进入看一下:
protected void onLayout(boolean changed, int l, int t, int r, int b) {
TraceCompat.beginSection(TRACE_ON_LAYOUT_TAG);
dispatchLayout();
TraceCompat.endSection();
mFirstLayoutComplete = true;
}
咱们进入 dispatchLayout 办法看下:
咱们进入这个 dispatchLayoutStep2 办法看下,这个办法终究调用到了
mLayout.onLayoutChildren(mRecycler, mState);
咱们进入 LinearLayoutManager 的 onLayoutChildren 办法看下,这个办法终究调用到了detachAndScrapAttachedViews 这个办法,咱们进入这个办法看下:
public void detachAndScrapAttachedViews(@NonNull Recycler recycler) {
final int childCount = getChildCount();
for (int i = childCount - 1; i >= 0; i--) {
final View v = getChildAt(i);
scrapOrRecycleView(recycler, i, v);
}
}
这儿调用了recycler.recycleViewHolderInternal(viewHolder); 和 recycler.scrapView(view); 咱们别离看下;
recycler.recycleViewHolderInternal(viewHolder) 主要用来处理 mCacheView 和 RecyclerViewPool 的缓存;
假如 ViewHolder remove、update 等发生变化的时分,不履行缓存逻辑;
recycleCachedViewAt 处理的便是 mCacheView;
// 假如 mCacheView 当前的巨细大于等于 mViewCacheMax(默认的mCacheView的巨细)
if (cachedViewSize >= mViewCacheMax && cachedViewSize > 0) {
recycleCachedViewAt(0);
cachedViewSize--;
}
void recycleCachedViewAt(int cachedViewIndex) {
if (DEBUG) {
Log.d(TAG, "Recycling cached view at index " + cachedViewIndex);
}
ViewHolder viewHolder = mCachedViews.get(cachedViewIndex);
if (DEBUG) {
Log.d(TAG, "CachedViewHolder to be recycled: " + viewHolder);
}
addViewHolderToRecycledViewPool(viewHolder, true);
mCachedViews.remove(cachedViewIndex);
}
addViewHolderToRecycledViewPool(viewHolder, true); 和mCachedViews.remove(cachedViewIndex); 这两个办法履行之后,会将 ViewHolder 添加到 RecycledViewPool 中(调用 addViewHolderToRecycledViewPool 办法),一起从 mCachedViews 中移除,也便是说 RecyclerViewPool 中的数据是从 mCachedView 中来的;
当 mCachedViews 中存满之后(默认寄存2个),就会把第 0 个方位的 View 添加到 RecyclerViewPool 中并从本身移除掉,第 1 个方位的 ViewHolder 移动到第 0 个方位,新进来的放到第 1 个方位;
咱们接下来看下addViewHolderToRecycledViewPool 办法的完成;
咱们进入 putRecycledView 办法中看下:
先获取 viewType,然后根据 viewType 获取 ScrapData,然后获取它的 scrapHeap 调集;也便是咱们的 ViewHolder 是寄存在 ScrapData 中了;
咱们来看下 getScrapDataForType 的办法完成:
if (mScrap.get(viewType).mMaxScrap <= scrapHeap.size()) {
return;
}
数据满了之后,直接 return 不进行缓存,也便是同一种 ViewType 的 ViewHolder 只保存 5 个;
scrap.resetInternal();
清空 ViewHolder,也便是缓存池中保存的只是 ViewHolder 类型,不保存数据;
咱们接下来看下recycler.scrapView(view);
这儿处理了 mAttachedScrap 和 mChangedScrap 用来缓存 ViewHolder;
全体时序图如下:
自定义LayoutManager
咱们假如想完成探探的左滑右滑作用,需要自定义 LayoutManager;
public class SlideLayoutManager extends RecyclerView.LayoutManager {
@Override
public RecyclerView.LayoutParams generateDefaultLayoutParams() {
return new RecyclerView.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.WRAP_CONTENT);
}
// 布局
@Override
public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
// ViewHodler收回复用
detachAndScrapAttachedViews(recycler);
int bottomPosition;
int itemCount = getItemCount();
if (itemCount < CardConfig.MAX_SHOW_COUNT) {
bottomPosition = 0;
} else {
// 布局了四张卡片
bottomPosition = itemCount - CardConfig.MAX_SHOW_COUNT;
}
for (int i = bottomPosition; i < itemCount; i++) {
// 复用
View view = recycler.getViewForPosition(i);
addView(view);
measureChildWithMargins(view, 0, 0);
int widthSpace = getWidth() - getDecoratedMeasuredWidth(view);
int heightSpace = getHeight() - getDecoratedMeasuredHeight(view);
// 布局 -> draw -> onDraw ,onDrawOver, onLayout
layoutDecoratedWithMargins(view, widthSpace / 2, heightSpace / 2,
widthSpace / 2 + getDecoratedMeasuredWidth(view),
heightSpace / 2 + getDecoratedMeasuredHeight(view));
int level = itemCount - i - 1;
if (level > 0) {
if (level < CardConfig.MAX_SHOW_COUNT - 1) {
view.setTranslationY(CardConfig.TRANS_Y_GAP * level);
view.setScaleX(1 - CardConfig.SCALE_GAP * level);
view.setScaleY(1 - CardConfig.SCALE_GAP * level);
} else {
// 最下面的那个view 与前一个view 布局相同
view.setTranslationY(CardConfig.TRANS_Y_GAP * (level - 1));
view.setScaleX(1 - CardConfig.SCALE_GAP * (level - 1));
view.setScaleY(1 - CardConfig.SCALE_GAP * (level - 1));
}
}
}
}
}
一个简略的自定义 LayoutManager, generateDefaultLayoutParams 直接抄系统的完成即可;
终究完成的作用如下:
仿 探探 的作用,gif 好卡…..;
简历润色
深度理解 RecyclerView 的缓存复用原理,可深度定制 LayoutManager;
下一章预告
带你玩转 ViewPager,完成炫酷 Banner;
欢迎三连
来都来了,点个关注,点个赞吧,你的支撑是我最大的动力~~~