这个是一个老问题了,这里记录一下,供自己参考学习
首先遇到这个问题,我们肯定会思考,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);
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);
}
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;
}
由于我们只关心它的高度,所以我们来看看里面具体测量高度的代码
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);
}
我们通过源代码可以看出,当竖直方向的测量模式为 MeasureSpec.AT_MOST 的时候,此时得到的测量高度heightSize 为measureHeightOfChildren(widthMeasureSpec, 0, NO_POSITION, heightSize, -1),而当我们进入这个方法查看的时候发现,它就是计算ListView的所有item的高度之和。但是当ListView嵌套在ScrollView里面的时候,显示高度只有一行,显然不是走这里的代码。
这时,我们再看看另外一种测量模式MeasureSpec.UNSPECIFIED,这种测量模式只在源代码中使用。当是这种测量模式的时候,测量高度heightSize 为 mListPadding.top + mListPadding.bottom + childHeight +getVerticalFadingEdgeLength() * 2。就是上内边距+下内边距+childHeight +上下边框高度。再看看childHeight ,往前看,我们又会看到这样几行代码
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());
}
什么意思?就是当widthMode 或heightMode 有一个测量模式为MeasureSpec.UNSPECIFIED的时候,那么就会只测量一个子item的高度,这下知道了,也就是当测量ListView高度的时候,如果说只显示了一行的高度,那么就是因为策略模式是MeasureSpec.UNSPECIFIED
那么问题又来了,为什么当ListView嵌套在ScrollView里面的时候,测量模式就变成了MeasureSpec.UNSPECIFIED了呢?
那么我们又开始思考了,控件的测量模式是父容器给的,是不是ScrollView在给ListView测量模式的时候除了问题?
我们来看看ScrollView的onMeasure方法
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
if (!mFillViewport) {
return;
}
final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
if (heightMode == MeasureSpec.UNSPECIFIED) {
return;
}
if (getChildCount() > 0) {
final View child = getChildAt(0);
final int widthPadding;
final int heightPadding;
final int targetSdkVersion = getContext().getApplicationInfo().targetSdkVersion;
final FrameLayout.LayoutParams lp = (LayoutParams) child.getLayoutParams();
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 desiredHeight = getMeasuredHeight() - heightPadding;
if (child.getMeasuredHeight() < desiredHeight) {
final int childWidthMeasureSpec = getChildMeasureSpec(
widthMeasureSpec, widthPadding, lp.width);
final int childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(
desiredHeight, MeasureSpec.EXACTLY);
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
}
}
我们在读代码的时候发现,当heightMode == MeasureSpec.UNSPECIFIED的时候就直接return了,所以我们继续往上看,进入super.onMeasure(widthMeasureSpec, heightMeasureSpec)方法,这时我们发现如下代码
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
if (mMeasureAllChildren || child.getVisibility() != 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);
}
}
}
}
这是逐个测量子View的一个for循环,我们只需要关注measureChildWithMargins方法,我们点进去继续查看(进入了ViewGroup),其代码如下
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 childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin
+ heightUsed, lp.height);
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
这时我们看到了child.measure(childWidthMeasureSpec, childHeightMeasureSpec),感觉胜利就在眼前,我们稍微往前看看
final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin
+ heightUsed, lp.height);
然后进入getChildMeasureSpec方法查看(源代码就不贴了,有点多),发现只有当ScrollView自己的测量模式为MeasureSpec.UNSPECIFIED的时候,才会给子View也传递MeasureSpec.UNSPECIFIED这个测量模式,但是前面我们有说了呀,ScrollView自己不可能是这个测量模式的,因为如果是这个测量模式,那么在自己的onMeasure方法的开始地方就return了回去。。。。。。
又开始思考,给子View测量的代码就在这里,这里不会给View传递MeasureSpec.UNSPECIFIED,所以,measureChildWithMargins应该被复写了!
经过查看,果然在ScrollView里面发现了该方法已被重写,我再贴出代码
@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方法
我给出代码,各位自己参详
public class MyListView extends ListView
{
public MyListView(Context context)
{
super(context);
}
public MyListView(Context context, AttributeSet attrs)
{
super(context, attrs);
}
public MyListView(Context context, AttributeSet attrs, int defStyle)
{
super(context, attrs, defStyle);
}
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
{
int expandSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2,
MeasureSpec.AT_MOST);
super.onMeasure(widthMeasureSpec, expandSpec);
}
}
版权声明:个人原创,若转载,请注明出处