源码分析_为啥ScrollView嵌套ListView,List

2018-03-25  本文已影响53人  拙峰朽木

这是以前常见的一个问题,现在可能很多人都不用ListView了。但是之前一直不是很清楚是什么原因。

为什么ListView只显示一个Item的高度

首先看下正常情况下ListView的高度是怎么算的。看下ListView的onMeasure()方法如何计算高的:

  @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        // Sets up mListPadding
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        .......
        if (heightMode == MeasureSpec.UNSPECIFIED) {
            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;
    }

正常情况下,高的Mode是不会是MeasureSpec.UNSPECIFIED,所以heightSize = measureHeightOfChildren(widthMeasureSpec, 0, NO_POSITION, heightSize, -1);不用看猜也能猜到是所以Item高的和,但是现在不正常了 我么看下MeasureSpec.UNSPECIFIED时,是如何计算高的

 heightSize = mListPadding.top + mListPadding.bottom + childHeight +
                    getVerticalFadingEdgeLength() * 2;

这个childHeight就是第一个Item的高度:

            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();

所以当ListView的高的Mode被设置为MeasureSpec.UNSPECIFIED时,它的高度就是第一个Item的高度加上一些padding值等了。这个跟我们看到ScrollView嵌套ListView看到的现象是一样。所以我们可以大胆猜测,ScrollView篡改了ListView高的Mode。 是这样吗?

事实就是这样,ScrollView重写了measureChild和measureChildWithMargins方法,在测量自己子View的时候会将高的Mode改成UNSPECIFIED。


  @Override
    protected void measureChild(View child, int parentWidthMeasureSpec,
            int parentHeightMeasureSpec) {
        ViewGroup.LayoutParams lp = child.getLayoutParams();

        int childWidthMeasureSpec;
        int childHeightMeasureSpec;

        childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec, mPaddingLeft
                + mPaddingRight, lp.width);
        final int verticalPadding = mPaddingTop + mPaddingBottom;
        childHeightMeasureSpec = MeasureSpec.makeSafeMeasureSpec(
                Math.max(0, MeasureSpec.getSize(parentHeightMeasureSpec) - verticalPadding),
                MeasureSpec.UNSPECIFIED);

        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    }

    @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);
        final int usedTotal = mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin +
                heightUsed;
        final int childHeightMeasureSpec = MeasureSpec.makeSafeMeasureSpec(
                Math.max(0, MeasureSpec.getSize(parentHeightMeasureSpec) - usedTotal),
                MeasureSpec.UNSPECIFIED);

        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    }

看到了吗,所以嵌套后的ListVIew高的Mode已经被改了

childHeightMeasureSpec = MeasureSpec.makeSafeMeasureSpec(
                Math.max(0, MeasureSpec.getSize(parentHeightMeasureSpec) - verticalPadding),
                MeasureSpec.UNSPECIFIED);

这应该就是这个现象最本质,最正确的解释。网上有各种解释,以前也没怎么看懂。现在撸源码顺便过了下,发现源码真是个好东西。

如何解决这个问题呢

其实解决这个问题的方式有很多,网上一搜各种都有。但是知道产生这个现象的原因后,我觉得最简单也是最好的解决方式就是将ListView的Mode改回去就行了,改成AT_MOST。

/**
 * 解决ScrollView嵌套后显示的高不正确问题
 * @author frc   on 2018/3/25.
 */

public class FrcListView extends ListView {
    public FrcListView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int heightSpec =MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2,MeasureSpec.AT_MOST);
        super.onMeasure(widthMeasureSpec, heightSpec);
  

就是这么简单。

上一篇 下一篇

猜你喜欢

热点阅读