相信很多android开发者,在之前的开发中会碰到ScrollView嵌套 ListView的时候,ListView只会显示一条,当时开发的时候,只是在网上找了如何解决,并没有去弄清楚为什么会只显示一条,最近在看一位大牛的资料的时候,听他一讲也就知道了原因,所以记录在这里;
ScrollView和ListView都是继承自ViewGroup的,那他们在绘制的时候都会调用onMeasure方法进行测量;
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
在onMeasure方法里面我们可以通过getMode和getSize获取宽高的模式和宽高的值;
//获取宽高的模式
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int heightMod = MeasureSpec.getMode(heightMeasureSpec);
//获取控件宽高值
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
这里说下宽高的默认,经常使用的为:MeasureSpec.AT_MOST和MeasureSpec.EXACTLY,MeasureSpec.UNSPECIFIED的话使用的就比较少,
MeasureSpec.AT_MOST相当于布局文件中的wrap_content
MeasureSpec.EXACTLY相当于在布局中的match_parent、fill_parent、100dp
MeasureSpec.UNSPECIFIED尽可能的大
而ScrollView在绘制的时候指定的模式就是MeasureSpec.UNSPECIFIED,所以ListView中onMeasure方法中通过MeasureSpec.getMode()的时候获取宽高的模式就是MeasureSpec.UNSPECIFIED,而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);
}
//在这里ListView根据获取到的模式做了判断
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.UNSPECIFIED的时候,就指定ListView的高度为top+bottom +childHeight +getVerticalFadingEdgeLength() * 2;这里childHeight 就是一个item条目的高度,也就是为什么只显示一个条目了,看到这里,就知道了,自定义ListView重写ListView的onMeasure方法,将它的模式改变为MeasureSpec.AT_MOST模式,这样就会去测量ListView所有item条目的高度,并将它绘制显示出来;
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);
}
}
在重写的时候改变heightMeasureSpec值和模式,值指定为Integer的最大值并右移两位(Integer.MAX_VALUE>>2),模式设置为MeasureSpec.AT_MOST;这样子就可以测量所有item的高度了,到这里ScrollView嵌套 ListView显示不全的问题就解决了,如上面写的有不对的地方,欢迎交流。