美文网首页Android技术知识Android进阶之路Android知识
【Android】ScrollView嵌套ListView只显示

【Android】ScrollView嵌套ListView只显示

作者: 指间沙似流年 | 来源:发表于2017-06-27 09:46 被阅读170次

    通常情况下我们在使用ScrollView嵌套ListView的时候,当出现问题的时候,相信绝大部分人都是在网上直接找别人的解决方案,都没有关心为什么会出现这种问题,为什么这样解决。

    通常情况下ScrollView嵌套ListView会出现只会显示ListView的第一个item的情况,而网上大部分的解决方式是:

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

    那么为什么会出现这样的情况呢?为什么这样解决呢?
    我们来一起分析分析:

    问题原因

    出现问题的时候我们会发现只显示了ListView的第一个item,而其他item都是可滑动出来的,猜测是由于onMeasure的问题导致的。

    所以我们先来看ScrollView的源码:

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
       super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    
       final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
       if (heightMode == MeasureSpec.UNSPECIFIED) {
           return;
       }
    
       if (getChildCount() > 0) {
           final View child = getChildAt(0);
           final int height = getMeasuredHeight();
           if (child.getMeasuredHeight() < height) {
               final int widthPadding;
               final int heightPadding;
               final FrameLayout.LayoutParams lp = (LayoutParams) child.getLayoutParams();
               final int targetSdkVersion = getContext().getApplicationInfo().targetSdkVersion;
               if (targetSdkVersion >= VERSION_CODES.M) {
                   widthPadding = mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin;
                   heightPadding = mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin;
               } else {
                   widthPadding = mPaddingLeft + mPaddingRight;
                   heightPadding = mPaddingTop + mPaddingBottom;
               }
    
               final int childWidthMeasureSpec = getChildMeasureSpec(
                       widthMeasureSpec, widthPadding, lp.width);
               final int childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(
                       height - heightPadding, MeasureSpec.EXACTLY);
               child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
           }
       }
    }
    

    通过源码可以看到ScrollView在测量的时候只拿了子View中的第一个,也就是ListView的高度。

    @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 (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;
    }
    

    通过ListView的onMeasure方法,我们看到:

    1. 首先判断itemCount是否大于0,如果大于0,计算第一个item的高度
    2. 由于heightMode是MeasureSpec.UNSPECIFIED,所以ListView的高度就是第一个item的高度加上相关的参数

    所以这就得出了为什么ScrollView嵌套ListView会出现显示不全的问题。

    如何解决

    通过源码我们知道了问题所在的原因,那么如何解决呢?

    这个时候就需要重写ListView,并覆盖onMeasure方法了

    1. 首先我们知道系统的mode是MeasureSpec.UNSPECIFIED,而我们又发现:

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

      ListView当mode是MeasureSpec.AT_MOST的时候会依次累计计算每个item的高度,所以我们需要设置mode为MeasureSpec.AT_MOST

    2. 那么为什么要设置高度为Integer.MAX_VALUE >> 2

      final int measureHeightOfChildren(int widthMeasureSpec, int startPosition, int endPosition,
         int maxHeight, int disallowPartialChildPosition) {
          
          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;
         }
      }
      

      在计算高度的时候ListView会累加每个item的高度,并最终和maxHeight比较,也就是Integer.MAX_VALUE >> 2,当小于maxHeight的时候,就直接返回ListView的真实高度,这个时候问题也就解决了。

    相关文章

      网友评论

        本文标题:【Android】ScrollView嵌套ListView只显示

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