這個(gè)是一個(gè)老問(wèn)題了,這里記錄一下,供自己參考學(xué)習(xí)
首先遇到這個(gè)問(wèn)題,我們肯定會(huì)思考,ListView只顯示了一行,是不是它的測(cè)量出了問(wèn)題?
我們首先看一下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;
}
由于我們只關(guān)心它的高度,所以我們來(lái)看看里面具體測(cè)量高度的代碼
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);
}
我們通過(guò)源代碼可以看出,當(dāng)豎直方向的測(cè)量模式為 MeasureSpec.AT_MOST 的時(shí)候,此時(shí)得到的測(cè)量高度heightSize 為measureHeightOfChildren(widthMeasureSpec, 0, NO_POSITION, heightSize, -1),而當(dāng)我們進(jìn)入這個(gè)方法查看的時(shí)候發(fā)現(xiàn),它就是計(jì)算ListView的所有item的高度之和。但是當(dāng)ListView嵌套在ScrollView里面的時(shí)候,顯示高度只有一行,顯然不是走這里的代碼。
這時(shí),我們?cè)倏纯戳硗庖环N測(cè)量模式MeasureSpec.UNSPECIFIED,這種測(cè)量模式只在源代碼中使用。當(dāng)是這種測(cè)量模式的時(shí)候,測(cè)量高度heightSize 為 mListPadding.top + mListPadding.bottom + childHeight +getVerticalFadingEdgeLength() * 2。就是上內(nèi)邊距+下內(nèi)邊距+childHeight +上下邊框高度。再看看childHeight ,往前看,我們又會(huì)看到這樣幾行代碼
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());
}
什么意思?就是當(dāng)widthMode 或heightMode 有一個(gè)測(cè)量模式為MeasureSpec.UNSPECIFIED的時(shí)候,那么就會(huì)只測(cè)量一個(gè)子item的高度,這下知道了,也就是當(dāng)測(cè)量ListView高度的時(shí)候,如果說(shuō)只顯示了一行的高度,那么就是因?yàn)椴呗阅J绞荕easureSpec.UNSPECIFIED
那么問(wèn)題又來(lái)了,為什么當(dāng)ListView嵌套在ScrollView里面的時(shí)候,測(cè)量模式就變成了MeasureSpec.UNSPECIFIED了呢?
那么我們又開始思考了,控件的測(cè)量模式是父容器給的,是不是ScrollView在給ListView測(cè)量模式的時(shí)候除了問(wèn)題?
我們來(lái)看看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);
}
}
}
我們?cè)谧x代碼的時(shí)候發(fā)現(xiàn),當(dāng)heightMode == MeasureSpec.UNSPECIFIED的時(shí)候就直接return了,所以我們繼續(xù)往上看,進(jìn)入super.onMeasure(widthMeasureSpec, heightMeasureSpec)方法,這時(shí)我們發(fā)現(xiàn)如下代碼
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);
}
}
}
}
這是逐個(gè)測(cè)量子View的一個(gè)for循環(huán),我們只需要關(guān)注measureChildWithMargins方法,我們點(diǎn)進(jìn)去繼續(xù)查看(進(jìn)入了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);
}
這時(shí)我們看到了child.measure(childWidthMeasureSpec, childHeightMeasureSpec),感覺(jué)勝利就在眼前,我們稍微往前看看
final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin
+ heightUsed, lp.height);
然后進(jìn)入getChildMeasureSpec方法查看(源代碼就不貼了,有點(diǎn)多),發(fā)現(xiàn)只有當(dāng)ScrollView自己的測(cè)量模式為MeasureSpec.UNSPECIFIED的時(shí)候,才會(huì)給子View也傳遞MeasureSpec.UNSPECIFIED這個(gè)測(cè)量模式,但是前面我們有說(shuō)了呀,ScrollView自己不可能是這個(gè)測(cè)量模式的,因?yàn)槿绻沁@個(gè)測(cè)量模式,那么在自己的onMeasure方法的開始地方就return了回去。。。。。。
又開始思考,給子View測(cè)量的代碼就在這里,這里不會(huì)給View傳遞MeasureSpec.UNSPECIFIED,所以,measureChildWithMargins應(yīng)該被復(fù)寫了!
經(jīng)過(guò)查看,果然在ScrollView里面發(fā)現(xiàn)了該方法已被重寫,我再貼出代碼
@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);
}
}
版權(quán)聲明:個(gè)人原創(chuàng),若轉(zhuǎn)載,請(qǐng)注明出處