美文网首页
ScrollView嵌套ListView显示不全的解决

ScrollView嵌套ListView显示不全的解决

作者: 刘孙猫咪 | 来源:发表于2017-06-24 17:10 被阅读0次

    在之前的开发中(没有recyclerview的时候)有时候会碰到ScrollView和ListView的嵌套使用,这个时候就会出现ListView只能显示一个条目,当时直接在网上找了解决方法,做法是自定义一个ListView重写它的onMeasure方法,也没有去研究为什么会这样,最近在看到一位大牛的资料的时候,其中就讲到为什么会出现这样的现象,所以就记录在这里。
    ScrollView和ListView都是系统帮我们已经实现了的控件,都是继承自ViewGroup,那么他们在测量的时候都会调用onMeasure方法,下面是
    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);
    
            int childWidth = 0;
            int childHeight = 0;
            int childState = 0;
    
            mItemCount = mAdapter == null ? 0 : mAdapter.getCount();
            if (mItemCount > 0 && (widthMode == MeasureSpec.UNSPECIFIED
                    || heightMode == MeasureSpec.UNSPECIFIED)) {
                final View child = obtainView(0, mIsScrap);
    
                // Lay out child directly against the parent measure spec so that
                // we can obtain exected minimum width and height.
                measureScrapChild(child, 0, widthMeasureSpec, heightSize);
    
                childWidth = child.getMeasuredWidth();
                childHeight = child.getMeasuredHeight();
                childState = combineMeasuredStates(childState, child.getMeasuredState());
    
                if (recycleOnMeasure() && mRecycler.shouldRecycleViewType(
                        ((LayoutParams) child.getLayoutParams()).viewType)) {
                    mRecycler.addScrapView(child, 0);
                }
            }
    
            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;
        }
    

    一开始我们就看到会调用MeasureSpec.getMode方法去获取父布局的宽高的模式,在这里的会有三种模式:

    MeasureSpec.AT_MOST  相当于wrap_content
    MeasureSpec.EXACTLY  相当于在布局中指定了值 或者match_parent 或者fill_parent
    MeasureSpec.UNSPECIFIED  尽可能的大
    

    前两种在开发中使用的比较多,后面那一种的话就使用的比较少,而对于ScrollView来说,它的模式就是MeasureSpec.UNSPECIFIED,所以ListView在获取宽高模式的时候获取到的就是MeasureSpec.UNSPECIFIED,而在ListView的onMeasure源码中对宽高模式做了判断:

           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);
            }
    

    如果获取到的宽高模式是MeasureSpec.UNSPECIFIED的时候,ListView的高度就是:

    heightSize = mListPadding.top + mListPadding.bottom + childHeight +
                        getVerticalFadingEdgeLength() * 2;
    

    所以就要重写ListView的onMeasure方法,改变宽高模式;

    public class MyListView extends ListView{
    
        public MyListView(Context context, AttributeSet attrs, int defStyleAttr) {
            super(context, attrs, defStyleAttr);
        }
    
        public MyListView(Context context) {
            super(context);
        }
    
        public MyListView(Context context, AttributeSet attrs) {
            super(context, attrs);
        }
    
        @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            //解决显示不全的问题
            heightMeasureSpec=MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE>>2,MeasureSpec.AT_MOST);
            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        }
    }
    

    在这里将宽高模式改为MeasureSpec.AT_MOST这样就会走源码中的这里去测量计算所有条目的高度:

    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);
            }
    
    

    在重写的时候我们还传入了一个Integer.MAX_VALUE>>2(Integer的最大值右移两位),在ListView的measureHeightOfChildren方法中对最大高度做了判断;
    measureHeightOfChildren()方法源码:

    final int measureHeightOfChildren(int widthMeasureSpec, int startPosition, int endPosition,
                int maxHeight, int disallowPartialChildPosition) {
            final ListAdapter adapter = mAdapter;
            if (adapter == null) {
                return mListPadding.top + mListPadding.bottom;
            }
    
            // Include the padding of the list
            int returnedHeight = mListPadding.top + mListPadding.bottom;
            final int dividerHeight = mDividerHeight;
            // The previous height value that was less than maxHeight and contained
            // no partial children
            int prevHeightWithoutPartialChild = 0;
            int i;
            View child;
    
            // mItemCount - 1 since endPosition parameter is inclusive
            endPosition = (endPosition == NO_POSITION) ? adapter.getCount() - 1 : endPosition;
            final AbsListView.RecycleBin recycleBin = mRecycler;
            final boolean recyle = recycleOnMeasure();
            final boolean[] isScrap = mIsScrap;
    
            for (i = startPosition; i <= endPosition; ++i) {
                child = obtainView(i, isScrap);
    
                measureScrapChild(child, i, widthMeasureSpec, maxHeight);
    
                if (i > 0) {
                    // Count the divider for all but one child
                    returnedHeight += dividerHeight;
                }
    
                // Recycle the view before we possibly return from the method
                if (recyle && recycleBin.shouldRecycleViewType(
                        ((LayoutParams) child.getLayoutParams()).viewType)) {
                    recycleBin.addScrapView(child, -1);
                }
    
                returnedHeight += child.getMeasuredHeight();
    
                if (returnedHeight >= maxHeight) {
                    // We went over, figure out which height to return.  If returnedHeight > maxHeight,
                    // then the i'th position did not fit completely.
                    return (disallowPartialChildPosition >= 0) // Disallowing is enabled (> -1)
                                && (i > disallowPartialChildPosition) // We've past the min pos
                                && (prevHeightWithoutPartialChild > 0) // We have a prev height
                                && (returnedHeight != maxHeight) // i'th child did not fit completely
                            ? prevHeightWithoutPartialChild
                            : maxHeight;
                }
    
                if ((disallowPartialChildPosition >= 0) && (i >= disallowPartialChildPosition)) {
                    prevHeightWithoutPartialChild = returnedHeight;
                }
            }
    
            // At this point, we went through the range of children, and they each
            // completely fit, so return the returnedHeight
            return returnedHeight;
        }
    
                if (returnedHeight >= maxHeight) {
                    // We went over, figure out which height to return.  If returnedHeight > maxHeight,
                    // then the i'th position did not fit completely.
                    return (disallowPartialChildPosition >= 0) // Disallowing is enabled (> -1)
                                && (i > disallowPartialChildPosition) // We've past the min pos
                                && (prevHeightWithoutPartialChild > 0) // We have a prev height
                                && (returnedHeight != maxHeight) // i'th child did not fit completely
                            ? prevHeightWithoutPartialChild
                            : maxHeight;
                }
    

    这样子的话就解决了ScrollView嵌套ListView显示不全的问题,如上面有写的不对的地方,欢迎交流。

    相关文章

      网友评论

          本文标题:ScrollView嵌套ListView显示不全的解决

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