這個是一個老問題了缕减,這里記錄一下雷客,供自己參考學習
首先遇到這個問題,我們肯定會思考烛卧,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;
}
由于我們只關(guān)心它的高度总放,所以我們來看看里面具體測量高度的代碼
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),而當我們進入這個方法查看的時候發(fā)現(xiàn),它就是計算ListView的所有item的高度之和灰粮。但是當ListView嵌套在ScrollView里面的時候,顯示高度只有一行蜈漓,顯然不是走這里的代碼穆桂。
這時,我們再看看另外一種測量模式MeasureSpec.UNSPECIFIED融虽,這種測量模式只在源代碼中使用享完。當是這種測量模式的時候,測量高度heightSize 為 mListPadding.top + mListPadding.bottom + childHeight +getVerticalFadingEdgeLength() * 2有额。就是上內(nèi)邊距+下內(nèi)邊距+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);
}
}
}
我們在讀代碼的時候發(fā)現(xiàn),當heightMode == MeasureSpec.UNSPECIFIED的時候就直接return了邦危,所以我們繼續(xù)往上看洋侨,進入super.onMeasure(widthMeasureSpec, heightMeasureSpec)方法,這時我們發(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);
}
}
}
}
這是逐個測量子View的一個for循環(huán)倦蚪,我們只需要關(guān)注measureChildWithMargins方法希坚,我們點進去繼續(xù)查看(進入了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方法查看(源代碼就不貼了,有點多)慕购,發(fā)現(xiàn)只有當ScrollView自己的測量模式為MeasureSpec.UNSPECIFIED的時候聊疲,才會給子View也傳遞MeasureSpec.UNSPECIFIED這個測量模式,但是前面我們有說了呀沪悲,ScrollView自己不可能是這個測量模式的获洲,因為如果是這個測量模式,那么在自己的onMeasure方法的開始地方就return了回去殿如。贡珊。最爬。。门岔。爱致。
又開始思考,給子View測量的代碼就在這里寒随,這里不會給View傳遞MeasureSpec.UNSPECIFIED糠悯,所以,measureChildWithMargins應(yīng)該被復寫了牢裳!
經(jīng)過查看逢防,果然在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)聲明:個人原創(chuàng)局嘁,若轉(zhuǎn)載,請注明出處