Android小疑问解答:为什么ScrollView嵌套ListView高度不正确
16lz
2021-01-26
Android小疑问解答:为什么ScrollView嵌套ListView高度不正确
- 前言
- 代码
- 布局的测绘过程
- 总结
前言
最近为了研究滑动冲突,所以就将ScrollView内部放了ListView。ListView高度设置为750dp。
结果一运行,什么贵,为什么我的listview高度就剩这么点了?说好的750dp呢?这糊鬼呢?
这是ScrollView的原因?但是ScrollView内部放其他控件,也没有这问题啊?
那这是ListView的原因?但是ListView放在LinearLayout等viewgroup内部,也没有这样 的问题啊?
所以,我带着这样的疑问看了一下ScrollView和ListView的测量源码。终于搞懂了为啥。
代码
首先老规矩,先上源码
布局
<?xml version="1.0" encoding="utf-8"?>
代码
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的高度直接采用第一个子元素的高度,
更多相关文章
- android 异步加载AsyncTask
- 四大组件之Activity小结
- Android的Listener监听事件分析
- Android中常见到的异常
- Android视图加载到窗口的过程
- Android调用so文件(C代码库)方法详解
- android: Handler概念理解与运用
- Android理解 Window 与 WindowManager
- Android(安卓)Gradle构建-理解DSL语言以及运行机制