RecyclerView
作者:互联网
ItemDecoration
定义:ItemDecoration允许应用给具体View添加具体的图画或者layout的偏移,对于绘制View之间的分割线。当调用addItemDecoration()方法添加decoration时,RecyclerView会调用该类的onDraw方法去绘制分割线(官方目前只有一个实现类DividerItemDecoration)。
自定义ItemDecoration,该类有几个重要的方法onDraw()和onDrawOver()进行绘制,区别就是onDraw()在预留控件绘制分割线在Item绘制之前,所以有可能被item覆盖,onDrawOver()则是在Item绘制之后。getItemOffsets()设置偏移量预留空间。eg:解析DividerItemDecoration,其onDraw方法可以判断垂直绘制和水品绘制,绘制过程中使用parent.getClipToPadding()
来判断是否可以在padding里面进行绘制,如果为true,则不能绘制。求出上下左右边界的值来设置边界mDivider.setBounds(left, top, right, bottom);
最终使用mDivider.draw(canvas);
进行绘制。getItemOffsets()方法实现如下,使用outRect.set()设置左上右下的预留空间来绘制分割线
if (mOrientation == VERTICAL) {
outRect.set(0, 0, 0, mDivider.getIntrinsicHeight());
} else {
outRect.set(0, 0, mDivider.getIntrinsicWidth(), 0);
}
RecyclerView的回收和复用
RecyclerView的适配器有两个方法onCrearViewHolder()和onBindViewHolder()
,它们涉及到RecyclerView的缓存,缓存的是ViewHolder。个人觉得RecyclerView是四级缓存:1. ChangeScrap和AttachedScrap缓存;2. CachedViews缓存;3. 自定义缓存;4. RecyclerViewPool缓冲池缓存。
回收源码
LinearLayoutMangager的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);
}
}
接着进入scrapOrRecycleView()方法内部,可以看到如下代码:
if (viewHolder.isInvalid() && !viewHolder.isRemoved()
&& !mRecyclerView.mAdapter.hasStableIds()) {
removeViewAt(index);
recycler.recycleViewHolderInternal(viewHolder);
} else {
detachViewAt(index);
recycler.scrapView(view);
mRecyclerView.mViewInfoStore.onViewDetached(viewHolder);
}
这段代码表示了在不同情况下处理不同的缓存,recycler.recycleViewHolderInternal(viewHolder)是用来处理CashedViews缓存和RecyclerViewPool缓存。当ViewHolder没有发生改变的时候,首先缓存到CashedView里面,而CashedView的缓存方式类似与队列(先进先出)。当CashedView缓存满的情况,会先出队列放入RecyclerViewPool缓冲池里,再入新的缓存进入队列。当ViewHolder发生了改变,则直接进入RecyclerViewPool缓冲池进行缓存,缓存方式类似与HashMap。缓存不同的ViewType,每一种ViewType最多只能缓存5个ViewHolder。多余的不会进行缓存;recycler.scrapView(view)用于处理ChangeScrap缓存和AttachedScrap缓存。当ViewHolder没有发生改变,缓存到AttachedSrap中,否则缓存到ChangeSrap中。
复用源码
怎么找复用的源码入口,回收和复用的产生必定是跟随着RecyclerView的滑动事件产生,只有滑动了才会产生回收和复用,因此可以找到onTouchEvent的Move事件下去寻找复用入口,可以找到如下代码块:
if (scrollByInternal(canScrollHorizontally ? dx : 0,canScrollVertically ? dy : 0,e)) {
getParent().requestDisallowInterceptTouchEvent(true);
}
进入到scrollByInternal方法内,可以找到下面的代码块,关注其中的scrollStep方法
if (mAdapter != null) {
mReusableIntPair[0] = 0;
mReusableIntPair[1] = 0;
scrollStep(x, y, mReusableIntPair);
consumedX = mReusableIntPair[0];
consumedY = mReusableIntPair[1];
unconsumedX = x - consumedX;
unconsumedY = y - consumedY;
}
紧接着再进入到scrollStep方法内去寻找,可以看到如下代码块,根据x和y方向做出不同方向的滑动,但本质上两个方法是一样的,只是方向的不同。
if (dx != 0) {
consumedX = mLayout.scrollHorizontallyBy(dx, mRecycler, mState);
}
if (dy != 0) {
consumedY = mLayout.scrollVerticallyBy(dy, mRecycler, mState);
}
选择其中一个方法进入,例如scrollVerticallyBy方法进去,可以看到如下代码:
public int scrollVerticallyBy(int dy, Recycler recycler, State state) {
return 0;
}
但是该方法时Layout的方法,继续找下去,找到LinearLayoutManager类的scrollVerticallyBy方法(GridLayoutManager是特殊的LinearLayoutManager),因此可以看到如下代码:
@Override
public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler,
ecyclerView.State state) {
if (mOrientation == HORIZONTAL) {
return 0;
}
return scrollBy(dy, recycler, state);
}
可以直接看到scrollBy方法,进入该方法继续寻找,可以看到这句代码final int consumed = mLayoutState.mScrollingOffset + fill(recycler, mLayoutState, state, false);
关注fill方法,RecyclerView的回收和复用就是在该方法里面进行处理,具体怎么处理,接着继续探究,可以看到如下代码:
while ((layoutState.mInfinite || remainingSpace > 0) && layoutState.hasMore(state)) {
layoutChunkResult.resetInternal();
if (RecyclerView.VERBOSE_TRACING) {
TraceCompat.beginSection("LLM LayoutChunk");
}
layoutChunk(recycler, state, layoutState, layoutChunkResult);
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
}
可以看到layoutChunk方法是在while方法循环进行调用,进入到layoutChunk方法内进行探究
View view = layoutState.next(recycler);
if (view == null) {
if (DEBUG && layoutState.mScrapList == null) {
throw new RuntimeException("received null view when unexpected");
}
// if we are laying out views in scrap, this may return null which means there is
// no more items to layout.
result.mFinished = true;
return;
}
RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) view.getLayoutParams();
if (layoutState.mScrapList == null) {
if (mShouldReverseLayout == (layoutState.mLayoutDirection
== LayoutState.LAYOUT_START)) {
addView(view);
} else {
addView(view, 0);
}
}
从上面可以看到通过layoutState.next(recycler)创建出View,然后再用addView方法把其添加进来,layoutState.next(recycler)就是具体的复用,再进入next方法里面可以看到如下代码:
View next(RecyclerView.Recycler recycler) {
if (mScrapList != null) {
return nextViewFromScrapList();
}
final View view = recycler.getViewForPosition(mCurrentPosition);
mCurrentPosition += mItemDirection;
return view;
}
其中getViewForPosition方法就是在指定的位置拿到对应的View,它是如何拿到对应的View,需要在进入到方法内部,可以看到如下代块:
@NonNull
public View getViewForPosition(int position) {
return getViewForPosition(position, false);
}
View getViewForPosition(int position, boolean dryRun) {
return tryGetViewHolderForPositionByDeadline(position, dryRun, FOREVER_NS).itemView;
}
tryGetViewHolderForPositionByDeadline方法就是获取到ViewHolder,然后再返回holder的itemView,RecyclerView所有的复用就是再tryGetViewHolderForPositionByDeadline方法里面进行处理.
标签:缓存,recycler,RecyclerView,View,方法,view 来源: https://blog.csdn.net/zou_sen/article/details/111993604