美文网首页
ScrollView嵌套ListView解决办法以及原理

ScrollView嵌套ListView解决办法以及原理

作者: 飞翔的超人 | 来源:发表于2019-03-29 14:40 被阅读0次

    1.解决办法

    继承ListView,并重写其中的onMeasure方法

    @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            heightMeasureSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2,
                    MeasureSpec.AT_MOST);
            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        }
    

    2.原理

    android的measure过程父控件遍历子控件,每个ViewGroup会向它内部的每个子View发送measure命令,然后由具体子View的onMeasure()来测量自己的尺寸。

    • ScrollView的onMeasure方法
        @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
            . . . . . .
        }
    

    先调用ScrollView父类FrameLayout的onMeasure

    • FrameLayout的onMeasure方法
        @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            int count = getChildCount();
    
            final boolean measureMatchParentChildren =
                    MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.EXACTLY ||
                    MeasureSpec.getMode(heightMeasureSpec) != MeasureSpec.EXACTLY;
            mMatchParentChildren.clear();
    
            int maxHeight = 0;
            int maxWidth = 0;
            int childState = 0;
    
            for (int i = 0; i < count; i++) {
                final View child = getChildAt(i);
                if (mMeasureAllChildren || child.getVisibility() != GONE) {
                    measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
                    final LayoutParams lp = (LayoutParams) child.getLayoutParams();
                    maxWidth = Math.max(maxWidth,
                            child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);
                    maxHeight = Math.max(maxHeight,
                            child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);
                    childState = combineMeasuredStates(childState, child.getMeasuredState());
                    if (measureMatchParentChildren) {
                        if (lp.width == LayoutParams.MATCH_PARENT ||
                                lp.height == LayoutParams.MATCH_PARENT) {
                            mMatchParentChildren.add(child);
                        }
                    }
                }
            }
    

    遍历所有子控件,并调用measureChildWithMargins方法让子View测量自身。ScrollView重写了measureChildWithMargins方法,measureChildWithMargins方法中可以看到给子View(这里就是ListView)的测量模式默认是MeasureSpec.UNSPECIFIED

        @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,
                    mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin
                            + widthUsed, lp.width);
            final int usedTotal = mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin +
                    heightUsed;
            final int childHeightMeasureSpec = MeasureSpec.makeSafeMeasureSpec(
                    Math.max(0, MeasureSpec.getSize(parentHeightMeasureSpec) - usedTotal),
                    MeasureSpec.UNSPECIFIED);
    
            child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
        }
    
    • ListView的onMeasure方法
     @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            // Sets up mListPadding
            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    
            final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
            final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
            int widthSize = MeasureSpec.getSize(widthMeasureSpec);
            int heightSize = MeasureSpec.getSize(heightMeasureSpec)
            . . . . . .
    
            if (widthMode == MeasureSpec.UNSPECIFIED) {
                widthSize = mListPadding.left + mListPadding.right + childWidth +
                        getVerticalScrollbarWidth();
            } else {
                widthSize |= (childState & MEASURED_STATE_MASK);
            }
    
            if (heightMode == MeasureSpec.UNSPECIFIED) {
                heightSize = mListPadding.top + mListPadding.bottom + childHeight +
                        getVerticalFadingEdgeLength() * 2;
            }
    
            if (heightMode == MeasureSpec.AT_MOST) {
                // TODO: after first layout we should maybe start at the first visible position, not 0
                heightSize = measureHeightOfChildren(widthMeasureSpec, 0, NO_POSITION, heightSize, -1);
            }
    
            setMeasuredDimension(widthSize, heightSize);
    
            mWidthMeasureSpec = widthMeasureSpec;
        }
    
    

    这里如果heightMode == MeasureSpec.UNSPECIFIED,heightSize给的是一个childHeight,所以ScrollView嵌套ListView后ListView只显示一行item。解决办法中将默认改为MeasureSpec.AT_MOST,ListView高度有多大就显示多大,最大不超过Integer.MAX_VALUE >> 2,因为Size最多只有20位,Integer.MAX_VALUE是32位,所以右移两位

    相关文章

      网友评论

          本文标题:ScrollView嵌套ListView解决办法以及原理

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