Android ScrollView中的ListView只显示一个条目问题

在ScrollView中放ListView时,ListView默认只显示一个item。
本文将根据这个问题来分析出现这个问题的原因及解决办法。

xml代码如下

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical" android:layout_width="match_parent"
    android:layout_height="match_parent">
    <ScrollView
        android:layout_width="match_parent"
        android:layout_height="match_parent">
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:orientation="vertical">
            <ListView
                android:id="@+id/lv"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content">

            </ListView>
            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="看我干啥,找挨揍啊!"/>
            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="没脸啊 还看啊"/>
        </LinearLayout>
    </ScrollView>
</LinearLayout>

Activity中的代码:

private void initView() {
        ListView listView = (ListView)findViewById(R.id.lv);
        ArrayList list  = new ArrayList();
        for(int i=0;i<30;i++){
            list.add("item"+i);
        }
        ArrayAdapter adapter = new ArrayAdapter(this,android.R.layout.simple_list_item_1,list);
        listView.setAdapter(adapter);
    }

效果图
这里写图片描述

那么为什么出现这样的现象呢?
在之前的博客Android UI绘制流程(二)曾经介绍过影响子View大小的因素不只是子View的MeasureSpec,还和父View的MeasureSpec相关。
要分析ListView的大小,首先要从ListView的父View来着手。所以首先来看ViewGroup中的getChildMeasureSpec()方法。

可以看到,在此方法中

 public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
        int specMode = MeasureSpec.getMode(spec);
        int specSize = MeasureSpec.getSize(spec);

        int size = Math.max(0, specSize - padding);

        int resultSize = 0;
        int resultMode = 0;

        switch (specMode) {
        case MeasureSpec.AT_MOST:
            if (childDimension >= 0) {
                resultSize = childDimension;
                resultMode = MeasureSpec.EXACTLY;                
            } else if (childDimension == LayoutParams.MATCH_PARENT) {
                resultSize = size;
                resultMode = MeasureSpec.AT_MOST;
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                resultSize = size;
                resultMode = MeasureSpec.AT_MOST;
            }
            break;
        }
        return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
    }

此方法决定Child的宽和高的MeasureSpec,而决定此MeasureSpec的因素是父View的MeasureSpec和子View的MeasureSpec。

1 子View的MeasureSpec能确定,即ListView中设置的:

 android:layout_width="match_parent"
 android:layout_height="match_parent"

2 而父View的Measurespec怎么确定呢?
同样的道理,父View的MeasureSpec是由父View的父View即ScrollView和父View即LinearLayout本身设定的宽高来共同决定的。所以追本溯源,来看ScrollView中的onMeasure()方法。

 @Override
    protected void measureChild(View child, int parentWidthMeasureSpec,
            int parentHeightMeasureSpec) {
        ViewGroup.LayoutParams lp = child.getLayoutParams();

        int childWidthMeasureSpec;
        int childHeightMeasureSpec;

        childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec, mPaddingLeft
                + mPaddingRight, lp.width);
        final int verticalPadding = mPaddingTop + mPaddingBottom;
        childHeightMeasureSpec = MeasureSpec.makeSafeMeasureSpec(
                Math.max(0, MeasureSpec.getSize(parentHeightMeasureSpec) - verticalPadding),
                MeasureSpec.UNSPECIFIED);

        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    }

可以看到其中的这段:

childHeightMeasureSpec = MeasureSpec.makeSafeMeasureSpec(
                Math.max(0, MeasureSpec.getSize(parentHeightMeasureSpec) - verticalPadding),
                MeasureSpec.UNSPECIFIED);

即ScrollView中的每个子布局的高度的SpecMode都是UNSPECIFIED的。即LinearLayout的heightSpecMode==UNSPECIFIED,现在已知LinearLaout的heightSpecMode了,下面再回头来看之前getChildMeasureSpec()中的这段:

 case MeasureSpec.UNSPECIFIED:
            if (childDimension >= 0) {
                resultSize = childDimension;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.MATCH_PARENT) {

                resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
                resultMode = MeasureSpec.UNSPECIFIED;
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
                resultMode = MeasureSpec.UNSPECIFIED;
            }
            break;

这段得出结论,当LinearLayout的heightSpecMode==UNSPECIFIED时,无论在 ListView中写match_parent还是wrap_content其实结果都一样,因为决定ListView的heightSpecSize并不是此参数,而是resultSize。而此时ListViewheightSpecMode=UNSPECIFIED。现在ListView的SpecMode已经去定了,我们先不考虑resultSize具体是多少(后面可以发现,其实当ListView中的SpecMode=UNSPECIFIED时,这个值并不能影响ListView的高度),先来看ListView中的onMeasure()方法:

 @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        childHeight = child.getMeasuredHeight();
        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;
        }
        setMeasuredDimension(widthSize, heightSize);

        mWidthMeasureSpec = widthMeasureSpec;
    }

这段代码不全,我只把需要的截取出来了。根据上面代码中的:

if (heightMode == MeasureSpec.UNSPECIFIED) {
            heightSize = mListPadding.top + mListPadding.bottom + childHeight +
                    getVerticalFadingEdgeLength() * 2;
        }

可以得出,当ListVIew的heightMoe==MeasureSpec.UNSPECIFIED时,其高度heightSize的高度为childHeight。至此已经找到了问题的源头。

解决办法:自定义ListView,并在onMeasure方法中修改heightMeasureSpec。这样就可以让ListView默认全部展开。

public class MyListView extends ListView {
    public MyListView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

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

再布局中使用自定义控件:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical" android:layout_width="match_parent"
    android:layout_height="match_parent">
    <ScrollView
        android:layout_width="match_parent"
        android:layout_height="match_parent">
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:orientation="vertical">
            <com.example.practice_click.MyListView
                android:id="@+id/lv"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content">

            </com.example.practice_click.MyListView>
            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="看我干啥,找挨揍啊!"/>
            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="没脸啊 还看啊"/>
        </LinearLayout>
    </ScrollView>
</LinearLayout>

效果图
这里写图片描述

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值