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的高度直接采用第一个子元素的高度,

更多相关文章

  1. android 异步加载AsyncTask
  2. 四大组件之Activity小结
  3. Android的Listener监听事件分析
  4. Android中常见到的异常
  5. Android视图加载到窗口的过程
  6. Android调用so文件(C代码库)方法详解
  7. android: Handler概念理解与运用
  8. Android理解 Window 与 WindowManager
  9. Android(安卓)Gradle构建-理解DSL语言以及运行机制

随机推荐

  1. android 截屏
  2. Android中Intent的各种常见作用
  3. App Inventor for Android(安卓)使用总结
  4. Android常用UI组件 - EditText
  5. android 触摸事件
  6. Android 强大的JSON助手 GSON
  7. Android 平台的 Microsoft Office & Adob
  8. Android BaseWebLoad组件使用及与js方法
  9. Google C2Dm相关文章
  10. Android自定义Button背景色,弧度