最新公告
  • 欢迎您光临AA分享网,一个高级程序员的学习、分享的分享平台!立即加入我们
  • 谈谈RecyclerView的LayoutManager-LinearLayoutManager源码分析

    今天我们来好好谈谈LayoutManager的问题。

    前言

    LayoutManager是RecyclerView用来管理子view布局的一个组件(另一个组件应该是Recycler,负责回收视图),它主要负责三个事情:

    1. 布局子视图

    2. 在滚动过程中根据子视图在布局中所处的位置,决定何时添加子视图和回收视图。

    3. 滚动子视图

    其中,只有滚动子视图,才会需要对子视图回收或者添加,而添加子视图则必然伴随着对所添加对象的布局处理。在滚动过程中,添加一次子视图只会影响到被添加对象,原有子视图的相对位置不会变化。 

    LayoutManager是RecyclerView的一个抽象内部类,一般我们使用它都是使用它的子类,常用的有LinearLayoutManager,GridLayoutManager,StaggeredGridLayoutManager,它们都是sdk自带的,实现了几种常用的布局。这里就不介绍它们的用法了。

    你也可以自定义一个LayoutManager,但是在你自定义之前,你必须分析现有的LayoutManager。这篇文章就是从分析LinearLayoutManager入手,来深入的理解LayoutManager这个东西。相信在看了LinearLayoutManager的源码之后,你对布局管理器会有深入的认识。

    准备工作

    我首先把SDK中LinearLayoutManager的源码copy了一份出来,重新命名为TestLayoutManager,然后解决了里面的错误(因为这个时候已经不在原来的包里了,一些类会找不到),这样我就能随意的在里面打log,修改代码看效果。我喜欢在读代码的同时去改代码:假如这里去掉,或者增加一些代码会发生什么情况。如果你需要,可以直接在这里下载我独立出来的这个TestLayoutManager http://jcodecraeer.oss-cn-shanghai.aliyuncs.com/cod/TestLayoutManager.java  。

    一些基本的知识

    不管是LinearLayoutManager,还是其它自定义的LayoutManager,这些方法基本都是逃不掉的:

    onLayoutChildren()

    onLayoutChildren()是 LayoutManager 的主入口。 它会在初始化布局时调用, 当适配器的数据改变时(或者整个适配器被换掉时)会再次调用。它的作用就是在初始化的时候放置item,直到填满布局为止。

    canScrollHorizontally() & canScrollVertically()

    这些方法很简单,在你想要滚动方向对应的方法里返回 true , 不想要滚动方向对应的方法里返回 false。

    scrollHorizontallyBy() & scrollVerticallyBy()

    在这里实现滚动的逻辑。RecyclerView 已经处理了触摸事件的那些事情,当你上下左右滑动的时候scrollHorizontallyBy() & scrollVerticallyBy()会传入此时的位移偏移量dy(或者dx), 根据这个dy你需要完成下面这三个任务:

    1. 将所有的子视图移动适当的位置 (对的,你得自己做这个)。

    2. 决定移动视图后 添加/移除 视图。

    3. 返回滚动的实际距离。框架会根据它判断你是否触碰到边界。

    开始

    LinearLayoutManager一共有2000多行代码,并不多,而且LinearLayoutManager需要处理纵向,横向,动画等问题,但是我们只关心它是如何做到管理布局的,其实关键的代码并不多,不会超过1000行。

    似乎我们该从onLayoutChildren方法开始对吧,因为它是入口嘛。本来期望里面是类似于添加view,为view设置位置的代码,应该很简单。但是看了onLayoutChildren方法的代码之后,一下子就受到10000点伤害,居然有近200行代码,而且完全不知所云。

    在碰壁之后,我觉得从RecyclerView的滚动过程开始分析。LinearLayoutManager支持横向和纵向滚动因此,它的canScrollHorizontally() 和 canScrollVertically()方法是这样实现的:

    @Override
    public boolean canScrollHorizontally() {
        return mOrientation == HORIZONTAL;
    }
    /**
     * @return true if [email protected] #getOrientation()} is [email protected] #VERTICAL}
     */
    @Override
    public boolean canScrollVertically() {
        return mOrientation == VERTICAL;
    }

    再来看看scrollHorizontallyBy() 和 scrollVerticallyBy()

    /**
     * [email protected]}
     */
    @Override
    public int scrollHorizontallyBy(int dx, RecyclerView.Recycler recycler,
                                    RecyclerView.State state) {
        if (mOrientation == VERTICAL) {
            return 0;
        }
        return scrollBy(dx, recycler, state);
    }
    /**
     * [email protected]}
     */
    @Override
    public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler,
                                  RecyclerView.State state) {
        if (mOrientation == HORIZONTAL) {
            return 0;
        }
        return scrollBy(dy, recycler, state);
    }

    可以看到,这两个方法都把滚动的处理交给了scrollBy方法,这个方法很短

    int scrollBy(int dy, RecyclerView.Recycler recycler, RecyclerView.State state) {
        if (getChildCount() == 0 || dy == 0) {
            return 0;
        }
        mLayoutState.mRecycle = true;
        ensureLayoutState();
        final int layoutDirection = dy > 0 ? LayoutState.LAYOUT_END : LayoutState.LAYOUT_START;
        final int absDy = Math.abs(dy);
        updateLayoutState(layoutDirection, absDy, true, state);
        final int consumed = mLayoutState.mScrollingOffset
                + fill(recycler, mLayoutState, state, false);
        if (consumed < 0) {
            if (DEBUG) {
                Log.d(TAG, "Don't have any more elements to scroll");
            }
            return 0;
        }
        final int scrolled = absDy > consumed ? layoutDirection * consumed : dy;
        mOrientationHelper.offsetChildren(-scrolled);
        if (DEBUG) {
            Log.d(TAG, "scroll req: " + dy + " scrolled: " + scrolled);
        }
        mLayoutState.mLastScrollDelta = scrolled;
        return scrolled;
    }

    当dy=0或者没有子元素的时候,什么也不做直接返回0。这个很好理解吧。

    然后根据dy判断滚动方向。如果是垂直布局的LinearLayoutManager的话,LayoutState.LAYOUT_END表示向下翻滚(手指向上划),反之LayoutState.LAYOUT_START表示向上翻滚。

    然后取dy的绝对值,并保存在absDy变量中。LinearLayoutManager在处理滚动的时候,都是用正整数来计算的,而不是用带有正负号的向量来计算。对于数学不好的人来说使用带正负号的数字太抽象了。

    在滚动的时候保存状态

    接下来调用了updatelayoutState方法,这个方法主要是完成一些状态的更新,在后面添加和回收视图的时候会把这些状态作为判断的条件。updatelayoutState方法代码不多,如下:

    private void updateLayoutState(int layoutDirection, int requiredSpace,
                                   boolean canUseExistingSpace, RecyclerView.State state) {
        mLayoutState.mInfinite = mOrientationHelper.getMode() == View.MeasureSpec.UNSPECIFIED;
        mLayoutState.mExtra = getExtraLayoutSpace(state);
        mLayoutState.mLayoutDirection = layoutDirection;
        int scrollingOffset;
        if (layoutDirection == LayoutState.LAYOUT_END) {
            mLayoutState.mExtra += mOrientationHelper.getEndPadding();
            // get the first child in the direction we are going
            final View child = getChildClosestToEnd();
            // the direction in which we are traversing children
            mLayoutState.mItemDirection = mShouldReverseLayout ? LayoutState.ITEM_DIRECTION_HEAD
                    : LayoutState.ITEM_DIRECTION_TAIL;
            mLayoutState.mCurrentPosition = getPosition(child) + mLayoutState.mItemDirection;
            mLayoutState.mOffset = mOrientationHelper.getDecoratedEnd(child);
            // calculate how much we can scroll without adding new children (independent of layout)
            scrollingOffset = mOrientationHelper.getDecoratedEnd(child)
                    - mOrientationHelper.getEndAfterPadding();
        } else {
            final View child = getChildClosestToStart();
            mLayoutState.mExtra += mOrientationHelper.getStartAfterPadding();
            mLayoutState.mItemDirection = mShouldReverseLayout ? LayoutState.ITEM_DIRECTION_TAIL
                    : LayoutState.ITEM_DIRECTION_HEAD;
            mLayoutState.mCurrentPosition = getPosition(child) + mLayoutState.mItemDirection;
            mLayoutState.mOffset = mOrientationHelper.getDecoratedStart(child);
            scrollingOffset = -mOrientationHelper.getDecoratedStart(child)
                    + mOrientationHelper.getStartAfterPadding();
        }
        mLayoutState.mAvailable = requiredSpace;
        if (canUseExistingSpace) {
            mLayoutState.mAvailable -= scrollingOffset;
        }
        mLayoutState.mScrollingOffset = scrollingOffset;
    }

    这些状态保存在mLayoutState变量中,下面是mLayoutState的各项数据代表的意思:

    • mLayoutState.mLayoutDirection 滑动方向

    • mLayoutState.mCurrentPosition 当前应该从adapter中获取item的position,用于在添加视图的时候,根据这个position从recycler中获取相应的View。

    • mLayoutState.mOffset 用于在添加布局的时候,根据它来确定被添加子View的布局位置。

    • mLayoutState.mAvailable 此次滚动发生后,

    • mLayoutState.mScrollingOffset 在添加一个新view之前,还可以滑动多少空间。

    其中mLayoutState.mScrollingOffset有点诡异。在下面的fill方法中又对它重新赋值:layoutState.mScrollingOffset += layoutState.mAvailable;

    导致实际上它等于dy。

    保存完状态之后,就进入fill方法。

        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.SCOLLING_OFFSET_NaN) {
                // TODO ugly bug fix. should not happen
                if (layoutState.mAvailable < 0) {
                    layoutState.mScrollingOffset += layoutState.mAvailable;
                    //Log.d(TAG, "layoutState.mScrollingOffset =" + layoutState.mScrollingOffset);
                }
                recycleByLayoutState(recycler, layoutState);
            }
            int remainingSpace = layoutState.mAvailable + layoutState.mExtra;
            LayoutChunkResult layoutChunkResult = new LayoutChunkResult();
            while ((layoutState.mInfinite || remainingSpace > 0) && layoutState.hasMore(state)) {
                layoutChunkResult.resetInternal();
                layoutChunk(recycler, state, layoutState, layoutChunkResult);
                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 || mLayoutState.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.SCOLLING_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;
        }

    fill做了两件事情:先回收移除不再显示的子View,然后添加即将进入可见区域的子View。

    回收过程

    在fill方法中首先重新设置了layoutState.mScrollingOffset的值,然后根据上面updatelayoutState方法所得到的状态对item进行回收,回收是通过recycleByLayoutState()方法实现的。recycleByLayoutState()方法的大致流程很简单,当一个item滚出了布局边界立即回收。其实这里不仅仅是做了回收,还把滚出页面的视图从RecyclerView移除。

    下面让我们来一步步分析recycleByLayoutState()的过程。

        private void recycleByLayoutState(RecyclerView.Recycler recycler, LayoutState layoutState) {
            if (!layoutState.mRecycle || layoutState.mInfinite) {
                return;
            }
            if (layoutState.mLayoutDirection == LayoutState.LAYOUT_START) {
                recycleViewsFromEnd(recycler, layoutState.mScrollingOffset);
            } else {
                recycleViewsFromStart(recycler, layoutState.mScrollingOffset);
            }
        }

    第一个if语句暂时不管它。

    在第二个if语句中,根据当前的滚动方向调用了不同的方法。如果是上拉(即LayoutState.LAYOUT_START)则调用recycleViewsFromEnd方法,从名字可以看出回收是从末尾开始的,这个很好理解,上拉的时候是查看前面的内容,底部的item 不断滚出界面,当然是回收末尾的view了;而如果是下拉(LayoutState.LAYOUT_END)则调用recycleViewsFromStart方法。

    顺藤摸瓜,进入recycleViewsFromStart(),一会儿再来看recycleViewsFromEnd(),其实原理都一样:

    private void recycleViewsFromStart(RecyclerView.Recycler recycler, int dt) {
        if (dt < 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 = dt;
        final int childCount = getChildCount();
        if (mShouldReverseLayout) {
            for (int i = childCount - 1; i >= 0; i--) {
                View child = getChildAt(i);
                if (mOrientationHelper.getDecoratedEnd(child) > limit) {// stop here
                    recycleChildren(recycler, childCount - 1, i);
                    return;
                }
            }
        } else {
            for (int i = 0; i < childCount; i++) {
                View child = getChildAt(i);
                if (mOrientationHelper.getDecoratedEnd(child) > limit) {// stop here
                    recycleChildren(recycler, 0, i);
                    return;
                }
            }
        }
    }

    这里的第二个参数dt即某时刻滚动距离的绝对值。如果你仔细看了前面的代码就知道它来自于layoutState.mScrollingOffset。虽然layoutState.mScrollingOffset本身代表的不是这个意思,但是代码里确实让它在此时等效于dy了。这也是我说layoutState.mScrollingOffset比较诡异的原因。

    然后把这个dt赋值给了limit,在这个方法里也许是多此一举,不过它主要是为了和recycleViewsFromEnd方法相统一。

    接着判断是否为mShouldReverseLayout,一般情况下都不是了,正常情况下是进入第二个条件。

    在第二个条件里是个for循环。这个for循环比较难懂。

    粗略的看就是遍历当前布局(RecyclerView)的子View,符合一定条件的子View就回收。

    执行回收的具体方法是recycleChildren()。

    但是符合什么条件才回收呢,还有就是 recycleChildren(recycler, 0, i)这个方法为什么有三个参数呢?

    回收一个view只需i这个参数就行了吧?

    mOrientationHelper.getDecoratedEnd(child)获得的是一个子view的底部边界的位置,我是根据log和方法名推测出来的,没有去深究它的实现。而> limit意思就是如果一旦一个子view的底部位置大于即将发生的位移(dt

    ),说明这个view在位移发生后,它仍然是可见的,那么就开始回收它之前的View,循环也到此结束(return)。for循环的作用就是跳过不可见的View。recycleChildren(recycler, 0, i)不是回收一个view,而是一堆view。但是实际上recycleChildren一次也只能回收了一个view。如果你从i=0开始分析(或者在recycleChildren中打log分析)就知道,不会存在累积很多个view才一起回收的情况,一有机会就回收了。

    我通过在recycleChildren中打log得出,在下拉时候,被回收的始终是child 0(第一个子View)。因为一旦回收了一个view,它随即也被从布局中移除,第二个立即变成了第一个。

    说完了recycleViewsFromStart()方法,还得简要的说下recycleViewsFromEnd(RecyclerView.Recycler recycler, int dt)方法啰,它和recycleViewsFromStart()相反是处理上拉的时候的回收,即LayoutState.LAYOUT_START。

    上拉的时候,我们是要看上面的item,底部的item逐渐消失,顶部item不断出现,这时该回收的是底部的item。这个方法的代码跟recycleViewsFromStart是完全一样的步骤,只是limit的计算变了,这里的limit = mOrientationHelper.getEnd() – dt;遍历的顺序也变了不是从0开始,而是从最后一个child开始,寻找第一个可见的child,一旦发现某个child的上边界在位移发生后还能小于limit,那么它就是可见的,它就是倒数第一个可见item,它之后的都是不可见的,调用recycleChildren(recycler, childCount – 1, i)把它们删除回收。

    所以你知道为什么recycleChildren方法需要三个参数了吧,因为它是删除一个区间,当然需要起始和末尾的索引啦,在加上参数recycler(用于回收)就是三个参数了。

    跟recycleViewsFromStart()方法一样,虽然这里回收一个区间,但是不会存在累积很多个view才一起回收的情况,被回收的始终是 childCount – 1。一旦有一个item就立马被回收了,然后被回收的item被它前面(或者后面一个)item替代。

    为了验证我的想法(不会存在累积很多个view才一起回收的情况),我在里面打了两个log,看看是否一次其实只回收了一个。

    没有判断if (DEBUG) 的那两个log才是我打的哈:

    下拉,一直都在回收第一个:

    上拉,一直都在回收第七个(即最后一个,具体最后一个是多少跟手机屏幕,布局大小有关):

    回收过程就这样结束了,接下来是视图的添加,注意回收和添加并没有什么因果关系,它们发生在两头,以下拉查看后面的内容为例,回收发生在顶部,而添加则发生在尾部。

    添加视图的过程

    让我们再回到fill方法:

    添加View的条件

    当一个子view完全显示出来,意味着下一个子view就要进来了,这个时候你就需要向布局中添加view。接下来的while循环就是添加View的过程。在添加之前,需要判断什么时候可以添加View。

    它有三个条件(有&&的也有||的),但是起决定因素的是remainingSpace > 0,因为layoutState.hasMore(state)和layoutState.mInfinite两个条件可以快速判断出来在多数情况下是一定的(看它们的源码就知道了)。所以我们这里就要去弄明白remainingSpace > 0到底是什么意思。

    remainingSpace = layoutState.mAvailable + layoutState.mExtra;

    其中 layoutState.mAvailable来自于上面提到的updatelayoutState方法, layoutState.mExtra可以暂时不用管,前面提到了 layoutState.mAvailable表示dy与最后一个View完全可见的所剩空间的差。但是并没有说明它怎么来的。我们还是再一次看看updateLayoutState方法吧:

    我们找到和layoutState.mAvailable相关的代码:

    mLayoutState.mAvailable = requiredSpace;
    if (canUseExistingSpace) {
        mLayoutState.mAvailable -= scrollingOffset;
    }

    其中requiredSpace就是某一刻滚动的距离即scrollby中的dy。

    canUseExistingSpace在滚动的时候始终为真的,所以

    mLayoutState.mAvailable -= scrollingOffset。那么现在的问题就是要搞清楚scrollingOffset是什么东西了。

    (1)当layoutDirection == LayoutState.LAYOUT_END
    scrollingOffset = mOrientationHelper.getDecoratedEnd(child)
    -mOrientationHelper.getEndAfterPadding();

    其中mOrientationHelper.getDecoratedEnd(child)表示获得child的下边界,而child=getChildClosestToEnd(),即最后一个可见元素。

    那么mOrientationHelper.getDecoratedEnd(child)的意思就是获得最后一个可见子view的下边界。

    mOrientationHelper.getEndAfterPadding()表示获得布局去除padding过后的底部边界。

    所以scrollingOffset就等于:最后一个可见视图的底部边界 - 布局去除padding过后的底部边界

    它代表什么意思呢?

    它代表最后一个可见View在当前滚动方向上还能滚动多远就完全可见了。

    而mLayoutState.mAvailable -= scrollingOffset就是用dy和它比较,当dy大于它,说明滚动发生后最后一个可见元素已经完全可见且离开了,该添加新的布局了。

    因为remainingSpace = layoutState.mAvailable + layoutState.mExtra;所以remainingSpace和mLayoutState.mAvailable的意思是一样的。因此remainingSpace > 0可以作为判断是否添加View的条件。

    (2)当layoutDirection == LayoutState.LAYOUT_START的时候

    这个时候我们则是计算布局顶部边界与第一个可见View的顶部边界,进而计算第一个可见子view什么时候完全可见。

    上面就是scrollingOffset的意思了:总结起来就是,在添加一个新的View之前,还能滚动多少距离,它包括了两个方向的情况。

    而mLayoutState.mAvailable -= scrollingOffset则是用dy和scrollingOffset比较,如果dy大于scrollingOffset的话,那么说明滚动发生后临近边界的view已经完全消耗完了,需要添加新的view了。

    现在回到remainingSpace,remainingSpace = layoutState.mAvailable + layoutState.mExtra;

    layoutState.mExtra可以忽略它。那么remainingSpace就相当layoutState.mAvailable,remainingSpace>0就表示移动发生后,需要添加一个新的View了。

    问题来了,为什么要用while循环而不是if语句呢?

    这是因为fill方法不只在滚动的时候被调用,初次布局的时候也被调用了,用while循环是为了初始化的时候不断的填满布局。

    添加View

    添加一个View是调用layoutChunk方法来完成的,让我们来看看这个layoutChunk方法:

    void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state,
                     LayoutState layoutState, LayoutChunkResult result) {
        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);
            }
        } else {
            if (mShouldReverseLayout == (layoutState.mLayoutDirection
                    == LayoutState.LAYOUT_START)) {
                addDisappearingView(view);
            } else {
                addDisappearingView(view, 0);
            }
        }
        measureChildWithMargins(view, 0, 0);
        result.mConsumed = mOrientationHelper.getDecoratedMeasurement(view);
        int left, top, right, bottom;
        if (mOrientation == VERTICAL) {
            if (isLayoutRTL()) {
                right = getWidth() - getPaddingRight();
                left = right - mOrientationHelper.getDecoratedMeasurementInOther(view);
            } else {
                left = getPaddingLeft();
                right = left + mOrientationHelper.getDecoratedMeasurementInOther(view);
            }
            if (layoutState.mLayoutDirection == LayoutState.LAYOUT_START) {
                bottom = layoutState.mOffset;
                top = layoutState.mOffset - result.mConsumed;
            } else {
                top = layoutState.mOffset;
                bottom = layoutState.mOffset + result.mConsumed;
            }
        } else {
            top = getPaddingTop();
            bottom = top + mOrientationHelper.getDecoratedMeasurementInOther(view);
            if (layoutState.mLayoutDirection == LayoutState.LAYOUT_START) {
                right = layoutState.mOffset;
                left = layoutState.mOffset - result.mConsumed;
            } else {
                left = layoutState.mOffset;
                right = layoutState.mOffset + result.mConsumed;
            }
        }
        // We calculate everything with View's bounding box (which includes decor and margins)
        // To calculate correct layout position, we subtract margins.
        layoutDecorated(view, left + params.leftMargin, top + params.topMargin,
                right - params.rightMargin, bottom - params.bottomMargin);
        if (DEBUG) {
            Log.d(TAG, "laid out child at position " + getPosition(view) + ", with l:"
                    + (left + params.leftMargin) + ", t:" + (top + params.topMargin) + ", r:"
                    + (right - params.rightMargin) + ", b:" + (bottom - params.bottomMargin));
        }
        // Consume the available space if the view is not removed OR changed
        if (params.isItemRemoved() || params.isItemChanged()) {
            result.mIgnoreConsumed = true;
        }
        result.mFocusable = view.isFocusable();
    }

    layoutChunk方法做了三件事:

    • 一是根据当前的position获得一个View。

    • 二是调用addView方法,addView顾名思义就是添加view了,不过它是LayoutManager的方法,最终还是要调用ViewGroup的addView方法;

    • 三是对刚刚添加的View进行布局。把它放置在恰当的位置。因为RecyclerView的item还包含了itemdecoration,LayoutManager提供了layoutDecorated方法来简化布局的过程。

    根据当前的position获取View的代码是

    View view = layoutState.next(recycler);

    它调用了layoutState的next方法来获取当前position的View:

    View next(RecyclerView.Recycler recycler) {
        if (mScrapList != null) {
            return nextViewFromScrapList();
        }
        final View view = recycler.getViewForPosition(mCurrentPosition);
        mCurrentPosition += mItemDirection;
        return view;
    }

    if里面语句的意思是如果mScrapList不为空,则直接从mScrapList中获取。暂时没有搞明白这个到底在什么情况下有用,因为我在这个if中写log从来没有被调用过。

    所以一般View还是从recycler的getViewForPosition(mCurrentPosition)中获取的。mCurrentPosition在前面已经解释了,它是在updatelayoutState方法中得到的。

    接下来用 mCurrentPosition += mItemDirection;更新mCurrentPosition的值,不过在滚动的时候这个貌似没有用呢。应该是用于初始化布局的时候吧。

    待续。。。

    AA分享网一个高级程序员的学习、分享的IT资源分享平台
    AA分享网-企业网站源码-PHP源码-网站模板-视频教程-IT技术教程 » 谈谈RecyclerView的LayoutManager-LinearLayoutManager源码分析
    • 262会员总数(位)
    • 5942资源总数(个)
    • 19本周发布(个)
    • 0 今日发布(个)
    • 565稳定运行(天)

    提供最优质的资源集合

    立即查看 了解详情