当前位置: 移动技术网 > IT编程>开发语言>Java > RecycleView 源码解析

RecycleView 源码解析

2020年07月24日  | 移动技术网IT编程  | 我要评论

ListView 能局部刷新吗?
可以。。。

和ListView 的区别? 强在哪里?
更加灵活

怎么布局的? 怎么就实现了可以列表 和 网格的切换?

是怎样的回收和重复利用View?

不管recycleView 是何方神圣,最终都会调用到onMeasure、onLayout

androidx.recyclerview.widget.LinearLayoutManager#fill

RecycleView 缓存机制

1.从mAttachedScrap 里面获取
mAttachedScrap 好像没有被使用
真的没有看到这个的作用在哪里。好像没有用。

2.mCachedViews 获取
用于存放最常用的3个,包括将要显示的那个,预加载的,以及刚回收的
GapWorker 预创建View 完成之后就会放到cached 里面

"main"@12,125 in group "main": RUNNING
recycleViewHolderInternal(RecyclerView$ViewHolder):6487, RecyclerView$Recycler {androidx.recyclerview.widget}, RecyclerView.java
recycleView(View):6369, RecyclerView$Recycler {androidx.recyclerview.widget}, RecyclerView.java
prefetchPositionWithDeadline(RecyclerView, int, long):295, GapWorker {androidx.recyclerview.widget}, GapWorker.java
flushTaskWithDeadline(GapWorker$Task, long):345, GapWorker {androidx.recyclerview.widget}, GapWorker.java
flushTasksWithDeadline(long):361, GapWorker {androidx.recyclerview.widget}, GapWorker.java
prefetch(long):368, GapWorker {androidx.recyclerview.widget}, GapWorker.java
run():399, GapWorker {androidx.recyclerview.widget}, GapWorker.java
handleCallback(Message):873, Handler {android.os}, Handler.java
dispatchMessage(Message):99, Handler {android.os}, Handler.java
loop():193, Looper {android.os}, Looper.java

3.mRecyclerPool 获取
用于存在每一种类型的,5个。和CacheView 作用不太一样。

4.ViewCacheExtension获取

为什么做这么多层级的缓存?意义在哪里?
难道是这一层级的,不需要绑定数据?可以直接用?

5.GapWorker 到底是干什么用的? 怎么干的?

6.绑定ViewHolder之后什么时候添加到View树上的?

androidx.recyclerview.widget.LinearLayoutManager#layoutChunk

里面会调用addView

7.怎么实现的?滑动就增加view? 怎么实现这么流畅的?
onMeasure,onLayout 不会调用吗?(不会)
为什么?

父容器RecycleView 是不会重新调用onMeasure,onLayout的,但是会调用item 的 onMeasure 和 on Layout.

androidx.recyclerview.widget.LinearLayoutManager#layoutChunk
但是不调用invalidate 会刷新吗?
他不会去刷新layout。

8.requestLayout 和 invalidate 什么区别? 分别什么时候调用比较好?
invalidate 会导致重新布局吗?
requestLayout 会导致 onDraw 调用吗?
//invalidate 不会重新onLayout onMeasure
// invalidate();
// 重新布局 onMeasure onLayout onDraw 都会重新调用
requestLayout();
9.view 怎么 移动的 ? 肯定不是scroll, 应该是调用layout方法。

offsetTopAndBottom(int):15964, View {android.view}, View.java
offsetChildrenVertical(int):5042, RecyclerView {androidx.recyclerview.widget}, RecyclerView.java
offsetChildrenVertical(int):9136, RecyclerView$LayoutManager {androidx.recyclerview.widget}, RecyclerView.java
offsetChildren(int):369, OrientationHelper$2 {androidx.recyclerview.widget}, OrientationHelper.java
scrollBy(int, RecyclerView$Recycler, RecyclerView$State):1399, LinearLayoutManager {androidx.recyclerview.widget}, LinearLayoutManager.java
scrollVerticallyBy(int, RecyclerView$Recycler, RecyclerView$State):1128, LinearLayoutManager {androidx.recyclerview.widget}, LinearLayoutManager.java
scrollStep(int, int, int[]):1841, RecyclerView {androidx.recyclerview.widget}, RecyclerView.java
scrollByInternal(int, int, MotionEvent):1940, RecyclerView {androidx.recyclerview.widget}, RecyclerView.java

10.offsetTopAndBottom 有什么优点?有什么作用?
快速移动? 不需要重新布局?只移动自己,不需要麻烦parent.

11.ainamator是怎么实现的?

12.具体的recycleView 的机制是什么?会提前加载上下一个列表项目吗?

13.滑动的时候,是先回收view还是先创建view 添加到底部?
首先,回收view, 然后添加下面的view,最后再回收view.

androidx.recyclerview.widget.RecyclerView#scrollBy

fill(RecyclerView$Recycler, LinearLayoutManager$LayoutState, RecyclerView$State, boolean):1576, LinearLayoutManager {androidx.recyclerview.widget}, LinearLayoutManager.java
scrollBy(int, RecyclerView$Recycler, RecyclerView$State):1391, LinearLayoutManager {androidx.recyclerview.widget}, LinearLayoutManager.java
scrollVerticallyBy(int, RecyclerView$Recycler, RecyclerView$State):1128, LinearLayoutManager {androidx.recyclerview.widget}, LinearLayoutManager.java
scrollStep(int, int, int[]):1841, RecyclerView {androidx.recyclerview.widget}, RecyclerView.java
scrollByInternal(int, int, MotionEvent):1940, RecyclerView {androidx.recyclerview.widget}, RecyclerView.java
onTouchEvent(MotionEvent):3391, RecyclerView {androidx.recyclerview.widget}, RecyclerView.java

    static class LayoutState {

        static final String TAG = "LLM#LayoutState";

        static final int LAYOUT_START = -1;

        static final int LAYOUT_END = 1;

        static final int INVALID_LAYOUT = Integer.MIN_VALUE;

        static final int ITEM_DIRECTION_HEAD = -1;

        static final int ITEM_DIRECTION_TAIL = 1;

        static final int SCROLLING_OFFSET_NaN = Integer.MIN_VALUE;

        /**
         * We may not want to recycle children in some cases (e.g. layout)
         */
        boolean mRecycle = true;

        /**
         * Pixel offset where layout should start
         * 应该接着哪里布局,接着那里添加view
         */
        int mOffset;

        /**
         * Number of pixels that we should fill, in the layout direction.
         * 当前需要填充的长度
         * 可以为负数 如果是负数 说明滑动的距离 不足够划出下一个展示的View
         */
        int mAvailable;

        /**
         * Current position on the adapter to get the next item.
         */
        int mCurrentPosition;

        /**
         * Defines the direction in which the data adapter is traversed.
         * Should be {@link #ITEM_DIRECTION_HEAD} or {@link #ITEM_DIRECTION_TAIL}
         */
        int mItemDirection;

        /**
         * Defines the direction in which the layout is filled.
         * Should be {@link #LAYOUT_START} or {@link #LAYOUT_END}
         */
        int mLayoutDirection;

        /**
         * Used when LayoutState is constructed in a scrolling state.
         * It should be set the amount of scrolling we can make without creating a new view.
         * Settings this is required for efficient view recycling.
         * 滚动多少范围内,不需要创建新的view
         */
        int mScrollingOffset;

        /**
         * Used if you want to pre-layout items that are not yet visible.
         * The difference with {@link #mAvailable} is that, when recycling, distance laid out for
         * {@link #mExtraFillSpace} is not considered to avoid recycling visible children.
         */
        int mExtraFillSpace = 0;

        /**
         * Contains the {@link #calculateExtraLayoutSpace(RecyclerView.State, int[])}  extra layout
         * space} that should be excluded for recycling when cleaning up the tail of the list during
         * a smooth scroll.
         */
        int mNoRecycleSpace = 0;

        /**
         * Equal to {@link RecyclerView.State#isPreLayout()}. When consuming scrap, if this value
         * is set to true, we skip removed views since they should not be laid out in post layout
         * step.
         */
        boolean mIsPreLayout = false;

        /**
         * The most recent {@link #scrollBy(int, RecyclerView.Recycler, RecyclerView.State)}
         * amount.
         */
        int mLastScrollDelta;

        /**
         * When LLM needs to layout particular views, it sets this list in which case, LayoutState
         * will only return views from this list and return null if it cannot find an item.
         */
        List<RecyclerView.ViewHolder> mScrapList = null;

        /**
         * Used when there is no limit in how many views can be laid out.
         */
        boolean mInfinite;

        /**
         * @return true if there are more items in the data adapter
         */
        boolean hasMore(RecyclerView.State state) {
            return mCurrentPosition >= 0 && mCurrentPosition < state.getItemCount();
        }

        /**
         * Gets the view for the next element that we should layout.
         * Also updates current item index to the next item, based on {@link #mItemDirection}
         *
         * @return The next element that we should layout.
         */
        View next(RecyclerView.Recycler recycler) {
            if (mScrapList != null) {
                return nextViewFromScrapList();
            }
            final View view = recycler.getViewForPosition(mCurrentPosition);
            mCurrentPosition += mItemDirection;
            return view;
        }

        /**
         * Returns the next item from the scrap list.
         * <p>
         * Upon finding a valid VH, sets current item position to VH.itemPosition + mItemDirection
         *
         * @return View if an item in the current position or direction exists if not null.
         */
        private View nextViewFromScrapList() {
            final int size = mScrapList.size();
            for (int i = 0; i < size; i++) {
                final View view = mScrapList.get(i).itemView;
                final RecyclerView.LayoutParams lp = (RecyclerView.LayoutParams) view.getLayoutParams();
                if (lp.isItemRemoved()) {
                    continue;
                }
                if (mCurrentPosition == lp.getViewLayoutPosition()) {
                    assignPositionFromScrapList(view);
                    return view;
                }
            }
            return null;
        }

        public void assignPositionFromScrapList() {
            assignPositionFromScrapList(null);
        }

        public void assignPositionFromScrapList(View ignore) {
            final View closest = nextViewInLimitedList(ignore);
            if (closest == null) {
                mCurrentPosition = RecyclerView.NO_POSITION;
            } else {
                mCurrentPosition = ((RecyclerView.LayoutParams) closest.getLayoutParams())
                        .getViewLayoutPosition();
            }
        }

        public View nextViewInLimitedList(View ignore) {
            int size = mScrapList.size();
            View closest = null;
            int closestDistance = Integer.MAX_VALUE;
            if (DEBUG && mIsPreLayout) {
                throw new IllegalStateException("Scrap list cannot be used in pre layout");
            }
            for (int i = 0; i < size; i++) {
                View view = mScrapList.get(i).itemView;
                final RecyclerView.LayoutParams lp = (RecyclerView.LayoutParams) view.getLayoutParams();
                if (view == ignore || lp.isItemRemoved()) {
                    continue;
                }
                final int distance = (lp.getViewLayoutPosition() - mCurrentPosition)
                        * mItemDirection;
                if (distance < 0) {
                    continue; // item is not in current direction
                }
                if (distance < closestDistance) {
                    closest = view;
                    closestDistance = distance;
                    if (distance == 0) {
                        break;
                    }
                }
            }
            return closest;
        }

        void log() {
            Log.d(TAG, "avail:" + mAvailable + ", ind:" + mCurrentPosition + ", dir:"
                    + mItemDirection + ", offset:" + mOffset + ", layoutDir:" + mLayoutDirection);
        }
    }
    /**
     * The magic functions :). Fills the given layout, defined by the layoutState. This is fairly
     * independent from the rest of the {@link LinearLayoutManager}
     * and with little change, can be made publicly available as a helper class.
     *
     * @param recycler        Current recycler that is attached to RecyclerView
     * @param layoutState     Configuration on how we should fill out the available space.
     * @param state           Context passed by the RecyclerView to control scroll steps.
     * @param stopOnFocusable If true, filling stops in the first focusable new child
     * @return Number of pixels that it added. Useful for scroll functions.
     */
    int fill(RecyclerView.Recycler recycler, LayoutState layoutState,
            RecyclerView.State state, boolean stopOnFocusable) {
        // max offset we should set is mFastScroll + available
        //记录最开始的时候  可用的空间
        final int start = layoutState.mAvailable;
        if (layoutState.mScrollingOffset != LayoutState.SCROLLING_OFFSET_NaN) {
            // TODO ugly bug fix. should not happen
            if (layoutState.mAvailable < 0) {
            //如果可用空间小于0  那么mScrollingOffset 就减去这个数
                layoutState.mScrollingOffset += layoutState.mAvailable;
            }
            recycleByLayoutState(recycler, layoutState);
        }
        int remainingSpace = layoutState.mAvailable + layoutState.mExtraFillSpace;
        LayoutChunkResult layoutChunkResult = mLayoutChunkResult;
        while ((layoutState.mInfinite || remainingSpace > 0) && layoutState.hasMore(state)) {
            layoutChunkResult.resetInternal();
            if (RecyclerView.VERBOSE_TRACING) {
                TraceCompat.beginSection("LLM LayoutChunk");
            }
            layoutChunk(recycler, state, layoutState, layoutChunkResult);
            if (RecyclerView.VERBOSE_TRACING) {
                TraceCompat.endSection();
            }
            if (layoutChunkResult.mFinished) {
                break;
            }
            layoutState.mOffset += layoutChunkResult.mConsumed * layoutState.mLayoutDirection;
            /**
             * Consume the available space if:
             * * layoutChunk did not request to be ignored
             * * OR we are laying out scrap children
             * * OR we are not doing pre-layout
             */
            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;
            }

            if (layoutState.mScrollingOffset != LayoutState.SCROLLING_OFFSET_NaN) {
                layoutState.mScrollingOffset += layoutChunkResult.mConsumed;
                if (layoutState.mAvailable < 0) {
                    layoutState.mScrollingOffset += layoutState.mAvailable;
                }
                recycleByLayoutState(recycler, layoutState);
            }
            if (stopOnFocusable && layoutChunkResult.mFocusable) {
                break;
            }
        }
        if (DEBUG) {
            validateChildOrder();
        }
        return start - layoutState.mAvailable;
    }

回收View 代码块解读

    private void recycleViewsFromStart(RecyclerView.Recycler recycler, int scrollingOffset,
            int noRecycleSpace) {
        if (scrollingOffset < 0) {
            if (DEBUG) {
                Log.d(TAG, "Called recycle from start with a negative value. This might happen"
                        + " during layout changes but may be sign of a bug");
            }
            return;
        }
        // ignore padding, ViewGroup may not clip children.
        final int limit = scrollingOffset - noRecycleSpace;
        final int childCount = getChildCount();
        if (mShouldReverseLayout) {
            for (int i = childCount - 1; i >= 0; i--) {
                View child = getChildAt(i);
                if (mOrientationHelper.getDecoratedEnd(child) > limit
                        || mOrientationHelper.getTransformedEndWithDecoration(child) > limit) {
                    // stop here
                    recycleChildren(recycler, childCount - 1, i);
                    return;
                }
            }
        } else {
            for (int i = 0; i < childCount; i++) {
                View child = getChildAt(i);
                //limit 表示滑动多少 就会创建新View
                //如果说你的底部小于limit 说明你肯定要被回收
                //直到找到一个View的底部>limit 的地方
                if (mOrientationHelper.getDecoratedEnd(child) > limit
                        || mOrientationHelper.getTransformedEndWithDecoration(child) > limit) {
                    // stop here
                    recycleChildren(recycler, 0, i);
                    return;
                }
            }
        }
    }

本文地址:https://blog.csdn.net/u013270444/article/details/105128002

如对本文有疑问, 点击进行留言回复!!

相关文章:

验证码:
移动技术网