這個是一個老問題了,這里記錄一下,供自己參考學習
首先遇到這個問題,我們肯定會思考,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);
}
}
版權聲明:個人原創,若轉載,請注明出處