事件分发机制-ScrollView嵌套ListView问题产生原理以及常见解决方式

来源:互联网 发布:批量注册淘宝小号软件 编辑:程序博客网 时间:2024/06/06 13:23

上一篇说明了事件分发的机制,接下来以一个实际会遇到的场景继续学习事件分发机制,
场景2:ScrollView嵌套ListView,listview只能显示一个item。

分析:

既然是高度问题的话那就先打开ScrollView查看一下onMeasure()方法:

 @Override    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {    //scrollview继承自FrameLayout,所以执行了framelayout的onMeasure()方法        super.onMeasure(widthMeasureSpec, heightMeasureSpec);    //mFillViewport默认为false        if (!mFillViewport) {            return;        }        ...

一步一步往下看,
FrameLayout的onMeasure():

 @Override    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {        int count = getChildCount();        final boolean measureMatchParentChildren =                MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.EXACTLY ||                MeasureSpec.getMode(heightMeasureSpec) != MeasureSpec.EXACTLY;        mMatchParentChildren.clear();        int maxHeight = 0;        int maxWidth = 0;        int childState = 0;        for (int i = 0; i < count; i++) {            final View child = getChildAt(i);            if (mMeasureAllChildren || child.getVisibility() != GONE) {            //最重要的是这一步,测量子view,且该方法scrollview已重写                measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);                final LayoutParams lp = (LayoutParams) child.getLayoutParams();                maxWidth = Math.max(maxWidth,                        child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);                maxHeight = Math.max(maxHeight,                        child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);                childState = combineMeasuredStates(childState, child.getMeasuredState());                if (measureMatchParentChildren) {                    if (lp.width == LayoutParams.MATCH_PARENT ||                            lp.height == LayoutParams.MATCH_PARENT) {                        mMatchParentChildren.add(child);                    }                }            }        }        ...

重要的就是measureChildWithMargins()这个方法,且srcollview已重写过,所以再看该方法:

@Override    protected void measureChildWithMargins(View child, int parentWidthMeasureSpec, int widthUsed,            int parentHeightMeasureSpec, int heightUsed) {        final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();//listview的宽模式        final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,                mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin                        + widthUsed, lp.width);        final int usedTotal = mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin +                heightUsed;//listview的长模式,(留意,这里强行的吧listview的模式设置UNSPECIFIED),这里就是让listview只显示一个item的最主要原因。        final int childHeightMeasureSpec = MeasureSpec.makeSafeMeasureSpec(                Math.max(0, MeasureSpec.getSize(parentHeightMeasureSpec) - usedTotal),                MeasureSpec.UNSPECIFIED);//测量child。//childWidthMeasureSpec、childWidthMeasureSpec就是listview的测量模式        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);    }

到这里我们知道,scrollview会强行设置他的childview的高模式为UNSPECIFIED,那接下来再看一下listview设置这种模式的话是如何计算其高度的:
listview-onMeasure():

if (heightMode == MeasureSpec.UNSPECIFIED) {            heightSize = mListPadding.top + mListPadding.bottom + childHeight +                    getVerticalFadingEdgeLength() * 2;        }

意思就是,在该测量模式下,listview的高度等于垂直方向上的padding+第一个子child的高度+??不影响判断。
至此scrollview嵌套listivew结果只显示一个item的原因已经分析清楚了。

小结:scrollview默认把childview设置为UNSPEFEIED模式,而该模式下的listview给自己的测量的高度就是第一个item的高度。

解决方案:
1.设置mFillViewport为true
看完整的ScrollView的onMeasure()代码:

 @Override    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {        super.onMeasure(widthMeasureSpec, heightMeasureSpec);//如果mFillViewport为true的话,则后面的代码都能执行,那么会有怎样的效果呢?        if (!mFillViewport) {            return;        }        final int heightMode = MeasureSpec.getMode(heightMeasureSpec);        if (heightMode == MeasureSpec.UNSPECIFIED) {            return;        }        if (getChildCount() > 0) {            final View child = getChildAt(0);            final int widthPadding;            final int heightPadding;            final int targetSdkVersion = getContext().getApplicationInfo().targetSdkVersion;            final FrameLayout.LayoutParams lp = (LayoutParams) child.getLayoutParams();            if (targetSdkVersion >= VERSION_CODES.M) {                widthPadding = mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin;                heightPadding = mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin;            } else {                widthPadding = mPaddingLeft + mPaddingRight;                heightPadding = mPaddingTop + mPaddingBottom;            }            final int desiredHeight = getMeasuredHeight() - heightPadding;            if (child.getMeasuredHeight() < desiredHeight) {                final int childWidthMeasureSpec = getChildMeasureSpec(                        widthMeasureSpec, widthPadding, lp.width);//测量模式为EXACTLY                final int childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(                        desiredHeight, MeasureSpec.EXACTLY);//留意childHeightMeasureSpec的测量模式                child.measure(childWidthMeasureSpec, childHeightMeasureSpec);            }        }    }

如果能执行到后面的代码,那么listview就能全部显示。也就是说,mFillViewport的值要设置为true就能解决问题。
可以直接在scrollview的布局中加属性fillViewport = true;也可以动态设置。

2.自定义ListView:
分析源码的目的就是要清楚产生问题的原因,从而能够针对性的提出解决办法,listview只显示一个也是因为自己在onMeasure()测量的问题造成的,毕竟是listview返回一个item的高度给scrollview,scrollview才显示一个item的高度的。所以我们也可以自定义Listview,并重写onMeasure()方法,返回正常的高度给ScrollView。
自定义的ListView只需重写onMeasure()方法即可:

@Override//>>2右移两位是因为MeasureSpec前2是表示模式,后30位才是尺寸        int customHeightSpec =         MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2,MeasureSpec.AT_MOST);        super.onMeasure(widthMeasureSpec, customHeightSpec);    }

还有一种思路就是在用LinearLayout包含listview,那么LinearLayout被设置为UNSPECFIED模式,此时listview必须设置精确的dp值。

如下图,当linearLayout为UNSPECIFIED时,listview必须是精确dp时才能避免也被设置为UNSPECIFIED模式。

这里写图片描述

这里并未涉及到更多的事件分发的机制。
下一篇我将分析ListView嵌套Scrollview产生的问题,来更好的理解事件分发机制。

感谢阅读!

阅读全文
0 0
原创粉丝点击