美文网首页Android工作中的问题记录
NestedScrollView嵌套RecyclerView导致

NestedScrollView嵌套RecyclerView导致

作者: gooddaytoyou | 来源:发表于2019-11-07 16:47 被阅读0次

    为什么会失效呢,该怎样去分析。

    这一块还得看NestedScrollView的代码及RecyclerView的复用原理。

    NestedScrollView

    @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    
            if (!mFillViewport) {
                return;
            }
    
            final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
            if (heightMode == MeasureSpec.UNSPECIFIED) {
                return;
            }
    
            if (getChildCount() > 0) {
                View child = getChildAt(0);
                final NestedScrollView.LayoutParams lp = (LayoutParams) child.getLayoutParams();
    
                int childSize = child.getMeasuredHeight();
                int parentSpace = getMeasuredHeight()
                        - getPaddingTop()
                        - getPaddingBottom()
                        - lp.topMargin
                        - lp.bottomMargin;
                //如果孩子高度小于父布局的高度,则对孩子的布局进行精确测量
                if (childSize < parentSpace) {
                    int childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,
                            getPaddingLeft() + getPaddingRight() + lp.leftMargin + lp.rightMargin,
                            lp.width);
                    int childHeightMeasureSpec =
                            MeasureSpec.makeMeasureSpec(parentSpace, MeasureSpec.EXACTLY);
                    child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
                }
            }
        }
    
    

    看下super.onMeasure(widthMeasureSpec, heightMeasureSpec)方法,NestedScrollView继承自FrameLayout,我们看FrameLayout的onMeasure方法。

    measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
    

    measureChildWithMargins这个方法是个关键,
    NestedScrollView重写了measureChildmeasureChildWithMargins两个方法。

     @Override
        protected void measureChild(View child, int parentWidthMeasureSpec,
                int parentHeightMeasureSpec) {
            ViewGroup.LayoutParams lp = child.getLayoutParams();
    
            int childWidthMeasureSpec;
            int childHeightMeasureSpec;
    
            childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec, getPaddingLeft()
                    + getPaddingRight(), lp.width);
    
            childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
    
            child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
        }
    
        @Override
        protected void measureChildWithMargins(View child, int parentWidthMeasureSpec, int widthUsed,
                int parentHeightMeasureSpec, int heightUsed) {
            final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
    
            final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
                    getPaddingLeft() + getPaddingRight() + lp.leftMargin + lp.rightMargin
                            + widthUsed, lp.width);
            final int childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(
                    lp.topMargin + lp.bottomMargin, MeasureSpec.UNSPECIFIED);
    
            child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
        }
    
    

    在这两个方法中,测量模式设置为MeasureSpec.UNSPECIFIED。当测量模式为MeasureSpec.UNSPECIFIED时,父容器没有对子View有任何限制,子View可以任意取尺寸,即大小有子View决定。

    RecyclerView

    来分析下RecyclerView中的代码,以LinearLayoutManager为例,RecyclerView是将绘制流程交给LayoutManager处理。

    LinearLayoutManager#onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) 
    
        ...
         mLayoutState.mInfinite = resolveIsInfinite();
        ....
         fill(recycler, mLayoutState, state, false);
    
    

    我们分析下resolveIsInfinite()方法,
    当父View是View.MeasureSpec.UNSPECIFIED的时候,则返回为true。

    boolean resolveIsInfinite() {
            return mOrientationHelper.getMode() == View.MeasureSpec.UNSPECIFIED
                    && mOrientationHelper.getEnd() == 0;
        }
    

    mLayoutState.mInfinite的值为true,我们定位
    fill的方法。

    
    //这里是关键,因为layoutState.mInfinite为true,因此会将getItemCount()得到的count数据都加载出来
    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.SCROLLING_OFFSET_NaN) {
                    layoutState.mScrollingOffset += layoutChunkResult.mConsumed;
                    if (layoutState.mAvailable < 0) {
                        layoutState.mScrollingOffset += layoutState.mAvailable;
                    }
                    recycleByLayoutState(recycler, layoutState);
                }
                if (stopOnFocusable && layoutChunkResult.mFocusable) {
                    break;
                }
            }
            
    

    layoutChunk方法

     //得到View,这里会走相关的缓存策略
      View view = layoutState.next(recycler);
      ...
      //添加View
    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);
                }
            }
    

    总结

    • 首先NestedScrollView对measureChildmeasureChildWithMargins两个方法进行重写,测量模式为MeasureSpec.UNSPECIFIED,当测量模式为MeasureSpec.UNSPECIFIED时,父容器没有对子View有任何限制,子View可以任意取尺寸,即大小有子View决定。
    • RecyclerView中onMeasure时,调用LayoutManager的onLayoutChildren方法中,将mLayoutState.mInfinite设置为true。
    • LayoutManager调用fill方法绘制时,会根据layoutState.mInfinite来判断,如果为true的话,会将会将getItemCount()得到的count数据都加载出来。

    相关文章

      网友评论

        本文标题:NestedScrollView嵌套RecyclerView导致

        本文链接:https://www.haomeiwen.com/subject/hlmvbctx.html