看源码:ScrollView嵌套ListView不会显示全

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

这里有两个函数,MeasureSpec.getMode(heightMeasureSpec)是用来取调用者设置大小的测量模式,其结果值为

        /**
         * 这个值可以理解为无限大的意思,平时很少用,一般会给ScrollView或者ListView这样的控件机算 
         * 大小使用
         */
        public static final int UNSPECIFIED = 0 << MODE_SHIFT;

        /**
         * 这个值就是在布局中设置大小给出了math_parent,fill_parent,或者一个固定大小的值 比如  50dp
         */
        public static final int EXACTLY     = 1 << MODE_SHIFT;

        /**
         * 这个值是在布局中给出了 pwrap_content
         */
        public static final int AT_MOST     = 2 << MODE_SHIFT;

还有一个函数是MeasureSpec.getSize(heightMeasureSpec),这是用来获取准确控件大小的值,这个很好理解。

ScrollView是继承自FrameLayout,先看看FrameLayout的onMeasure方法

      for (int i = 0; i < count; i++) {
            final View child = getChildAt(i);
            if (mMeasureAllChildren || child.getVisibility() != GONE) {
            	//这里很好理解,就是子空间不是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);
                    }
                }
            }
        }

而ScrollView也重写了measureChildWithMargins()这个方法

   @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当测量模式给出无限大的情况下,怎么计算高度

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

所以,当ScrollView嵌套ListView在不做任何处理的情况下,能看到的仅仅是大约一个多item的高度。

解决方案其实有很多个版本,网上能查到最多的版本是继承一个ListView,然后重写onMeasure方法

  @Override
    /**
     * 重写该方法,达到使ListView适应ScrollView的效果
     */
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int expandSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2,
        MeasureSpec.AT_MOST);
        super.onMeasure(widthMeasureSpec, expandSpec);
    }

但是很多同学其实不知道为什么这样去解决,首先我们要看代码,怎样才能让这种情况下的ListView有一个可算的高度

       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;
        }
        
        //一定是要进入到这个方法,heightSize才会有一个具体的值计算出来    
        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);

所以测量模式确定了,肯定是AT_MOST

        //长度30
        private static final int MODE_SHIFT = 30;
        
        .................
           
        /**
         * Measure specification mode: The child can be as large as it wants up
         * to the specified size.
         */
        public static final int AT_MOST     = 2 << MODE_SHIFT;

android中把测量出的int做了处理,int类型的长度是32位,把前两位作为标记位,标记了测量模式,如EXACTLY、AT_MOST,把最后30位作为测量的具体高度或者是宽度。所以我们在调用MeasureSpec.makeMeasureSpec(size,mode)方法时,传入的size参数要把Integer.MAX_VALUE右移2位,因为前两位会被认为是标志,而不是值。

       //这里是取了前两位的测量模式
       final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        //这里是取了后30位的测量值   
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);

其实,ScrollView也不知道子控件的实际大小到底有多大,所以这里我们尽量给出一个相对较大的值。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值