在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>
效果图