ScrollView巢狀ListView導致item顯示不全的原因
一句話總結:
ScrollView重寫了它的父類FrameLayout的measureChild和measureChildWithMargins方法,使傳入子類的HeightMeasureSpec的模式為UNSPECIFIED,導致listview計算高度時跳過了measureHeightOfChildren方法的執行,只計算了第一個item的高度。
分析:
首先看listview的onMeasure方法中關於高度的賦值:
1276行:
int heightSize = MeasureSpec.getSize(heightMeasureSpec);//從heightMeasureSpec中獲取高度
1308行:
//如果為UNSPECIFIED則高度為第一個child的高度加上邊距
if (heightMode == MeasureSpec.UNSPECIFIED) {
heightSize = mListPadding.top + mListPadding.bottom + childHeight +
getVerticalFadingEdgeLength() * 2;
}
1313行:
//如果為AT_MOST則去執行measureHeightOfChildren方法,計算最大高度
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);
}
可以看出listview的高度計算取決於傳入onMeasure方法的MeasureSpec的mode型別。通過了解View的測量流程可以知道,onMeasure方法在measure方法中被呼叫。而measure方法一般被其父view呼叫。此巢狀情況下,listview的父view是scrollview,那麼現在去看一下scrollview中onMeasure的情況:
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
if (!mFillViewport) {
return;
}
......
注意開始的這兩行判斷,mFillViewport預設是false的,在我們沒有人為設定的情況下,onMeasure執行到這裡就return了,下面的程式碼是不會執行的。所以真正執行的應該只有super.onMeasure(widthMeasureSpec, heightMeasureSpec);呼叫父類的onMeasure方法,ScrollView的父類是Framlayout,Framlayout的高度測量呼叫的是ViewGroup提供的getChildMeasureSpec方法。此方法內只有當父view的MeasureSpec的mode為UNSPECIFIED時,子view才有可能被賦予UNSPECIFIED。(通過實際測試,FrameLayout巢狀ListView並不會對ListView產生影響。通過DEBUG也發現ScrollView呼叫父類onMeasure方法時傳入的heightMeasureSpec的mdoe也並非是UNSPECIFIED)。那麼可以確定,還是ScrollView自身對ListView產生了影響,與FrameLayout關係不大。那麼視線就轉到了ScrollView對FrameLayout的方法的重寫上:
@Override
protected void measureChild(View child, int parentWidthMeasureSpec,
int parentHeightMeasureSpec) {
......
childHeightMeasureSpec = MeasureSpec.makeSafeMeasureSpec(
Math.max(0, MeasureSpec.getSize(parentHeightMeasureSpec) - verticalPadding),
MeasureSpec.UNSPECIFIED);
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
@Override
protected void measureChildWithMargins(View child, int parentWidthMeasureSpec, int widthUsed,int parentHeightMeasureSpec, int heightUsed) {
......
final int childHeightMeasureSpec = MeasureSpec.makeSafeMeasureSpec(
Math.max(0, MeasureSpec.getSize(parentHeightMeasureSpec) - usedTotal),
MeasureSpec.UNSPECIFIED);
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
可以看到ScrollView重寫了measureChild和measureChildWithMargins方法,並將childHeightMeasureSpec的mode強制更改為UNSPECIFIED。呼叫的父類的onMeasure方法中執行的measureChildWithMargins方法其實是ScrollView重寫過的。最終導致ListView的heightMode為UNSPECIFIED。