欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

RecycleView 源码解析

程序员文章站 2022-03-21 17:24:02
ListView 能局部刷新吗?可以。。。...

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

相关标签: Android