其他分享
首页 > 其他分享> > RecyclerView

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