ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

Android小疑问解答:为什么ScrollView嵌套ListView高度不正确

2019-07-25 11:40:39  阅读:364  来源: 互联网

标签:MeasureSpec int ScrollView child Android ListView widthMeasureSpec


Android小疑问解答:为什么ScrollView嵌套ListView高度不正确

前言

最近为了研究滑动冲突,所以就将ScrollView内部放了ListView。ListView高度设置为750dp。
结果一运行,什么贵,为什么我的listview高度就剩这么点了?说好的750dp呢?这糊鬼呢?

这是ScrollView的原因?但是ScrollView内部放其他控件,也没有这问题啊?
那这是ListView的原因?但是ListView放在LinearLayout等viewgroup内部,也没有这样 的问题啊?

所以,我带着这样的疑问看了一下ScrollView和ListView的测量源码。终于搞懂了为啥。

代码

首先老规矩,先上源码
布局

<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".view.ViewInterceptTestActivity">
    <ListView
        android:layout_width="match_parent"
        android:layout_height="750dp"
        android:id="@+id/list_intercept_test"
        >
    </ListView>
</ScrollView>

代码

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

标签:MeasureSpec,int,ScrollView,child,Android,ListView,widthMeasureSpec
来源: https://blog.csdn.net/qq_17810899/article/details/97243722

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有