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

上一篇说明了事件分发的机制,接下来以一个实际会遇到的场景继续学习事件分发机制,
场景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
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值