最近为了研究滑动冲突,所以就将ScrollView内部放了ListView。ListView高度设置为750dp。
结果一运行,什么贵,为什么我的listview高度就剩这么点了?说好的750dp呢?这糊鬼呢?
这是ScrollView的原因?但是ScrollView内部放其他控件,也没有这问题啊?
那这是ListView的原因?但是ListView放在LinearLayout等viewgroup内部,也没有这样 的问题啊?
所以,我带着这样的疑问看了一下ScrollView和ListView的测量源码。终于搞懂了为啥。
首先老规矩,先上源码
布局
代码
public class ViewInterceptTestActivity extends AppCompatActivity {
private static final String TAG = "ViewInterceptTestActivi";
private ListView listView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_view_intercept_test);
listView= (ListView) findViewById(R.id.list_intercept_test);
ArrayList list=new ArrayList();
for (int i=0;i<99;i++){
list.add("i=="+i);
}
ArrayAdapter arrayAdapter=new ArrayAdapter(this,android.R.layout.simple_list_item_1,list);
listView.setAdapter(arrayAdapter);
listView.post(new Runnable() {
@Override
public void run() {
Log.d(TAG, "run: listviewHeight"+listView.getMeasuredHeight()); // 打印ListView的测量高度
Log.d(TAG, "run: listviewFatherHeight"+((ViewGroup)listView.getParent()).getMeasuredHeight()); //打印ScrollView高度
}
});
如果想看完整的测绘原理和过程,可以查看我之前写的view工作原理的笔记。这里只从ScrollView的onMeasure方法开始看
ScrollView
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//由于ScrollView是FrameLayout的子类,所以这里调用了ViewGroup的onMeasure
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
//这里代码,先暂时不看一会儿再看
}
FrameLayout
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int count = getChildCount();
//....
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
if (mMeasureAllChildren || child.getVisibility() != GONE) {
//这里ScrollView 重写了measureChildWithMargins方法。
measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
//.....
}
}
//....
}
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);
//这里需要大家注意一点很特殊的地方,就是childHeightMeasureSpec 最后构建的时候传递的是UNSPECIFIED类型
//这就是和父类measureChildWithMargins方法不一样的地方
final int childHeightMeasureSpec = MeasureSpec.makeSafeMeasureSpec(
MeasureSpec.getSize(parentHeightMeasureSpec), MeasureSpec.UNSPECIFIED);
//调用了子元素的measure,也就是我们这里ListView的measure。
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
这里的measure方法就是View中的measure方法,所以根据我们已有知道,知道最后的measure处理,还是在onMeasure中,所以这里我们直接看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();
//还记得,我们上面ScrollView给ListView传递的mode是UNSPECIFIED了吗?
//现在就走到这个方法里面了
if (mItemCount > 0 && (widthMode == MeasureSpec.UNSPECIFIED
|| heightMode == MeasureSpec.UNSPECIFIED)) {
//取出Listview第一个元素的高度
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) {
//如果高度的mode为UNSPECIFIED,那么listview 的高度就为第一个childHeight的高度加上padding等。
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;
}
所以,ScrollView 嵌套listview导致listview高度不正常的原因就是水落石出了。就是由于ScrollView将子元素的高度测量模式都更改为UNSPECIFIED。而Listview中,如果测量模式为UNSPECIFIED,则listview的高度直接采用第一个子元素的高度,