自定义ListView(被ScrollView嵌套),从源码入手,彻底理解其onMeasure方法为什么要这么写?

彻底理解其onMeasure方法,主要理解ListView的高度是怎么得来的

结论

1.先上自定义ListView中onMeasure方法的代码,再说我的结论吧!

    public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    
    
        int expandSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2, MeasureSpec.AT_MOST);
        super.onMeasure(widthMeasureSpec, expandSpec);
    }

结论:当ListView的(各个列表项的高度之和 + 各项间的间隔之和)小于Integer.MAX_VALUE >> 2时,则ListView的高度就会设置为(各列表项的高度 + 各项的间隔)

论证

分析makeMeasureSpec方法

先分析

int spec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2, MeasureSpec.AT_MOST);

其他博客应该也讲得很清楚了, 我这里就只根据这里的参数简要地讲一讲。
先看一下makeMeasureSpec它的源码

//大小(size)+模式(mode),前两位是表示模式, 后30位是表示大小。
//都是用二进制进行运算。
public static int makeMeasureSpec(int size,int mode) {
    
    
			//判断是否是17版本或者更低的版本,仅仅只是为了适配低版本,因为低版本有点bug。
			//但这并不会影响最终得到的结果。两个分支得到的结果都会是一样的。
            if (sUseBrokenMakeMeasureSpec) {
    
    
                return size + mode;
            } else {
    
    
                return (size & ~MODE_MASK) | (mode & MODE_MASK);
            }
        }

Integer.MAX_VALUE >> 2这个参数表示大小(size)

因为:

最大的整数往右移动两位,得到001…(后面全是1,共有30个1),正好它就可以表示大小(size )。因为最前面的两个0可有可无,对原本的数据不会产生影响。
而MeasureSpec.AT_MOST正好表示模式(mode)。所以正好对应了makeMeasureSpec的两个参数。
可能到了这里,会有很多人不理解,自定义ListView中其onMeasure方法,第二个参数为什么要为 MeasureSpec.AT_MOST,当为MeasureSpec.EXACTLY,MeasureSpec.UNSPECIFIED时,为什么就得不到ListView正确的高度呢?听我下面慢慢道来。

分析super.onMeasure方法

分析super.onMeasure(widthMeasureSpec, expandSpec);这一行代码。很多人分析自定义ListView的时候都没有分析这行代码,都只分析了MeasureSpec方法,反正我认为这恰恰是理解整个onMeasure方法的重点。

源码1

 @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();
        if (mItemCount > 0 && (widthMode == MeasureSpec.UNSPECIFIED
                || heightMode == MeasureSpec.UNSPECIFIED)) {
    
    
            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) {
    
    
            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;
    }

源码摘要1

着重观察如下这几行代码
(1)

//根据自定义ListView的onMeasure方法,得到高度的模式
final int heightMode = MeasureSpec.getMode(heightMeasureSpec);

(2)

//得到高度的大小,根据自定义ListView的onMeasure方法
//heightSize = Integer.MAX_VALUE >> 2
 int heightSize = MeasureSpec.getSize(heightMeasureSpec);

(3)

  if (heightMode == MeasureSpec.AT_MOST) {
    
    
		//前往这个方法内部
            heightSize = measureHeightOfChildren(widthMeasureSpec, 0, NO_POSITION, heightSize, -1);
        }

源码2

所以接下来跳转到measureHeightOfChildren方法中。在这个方法里面,就会得到真正的ListView的高度,也是主要分析部分代码

final int measureHeightOfChildren(int widthMeasureSpec, int startPosition, int endPosition,
            int maxHeight, int disallowPartialChildPosition) {
    
    
        final ListAdapter adapter = mAdapter;
        if (adapter == null) {
    
    
            return mListPadding.top + mListPadding.bottom;
        }

        // Include the padding of the list
        int returnedHeight = mListPadding.top + mListPadding.bottom;
        final int dividerHeight = mDividerHeight;
        // The previous height value that was less than maxHeight and contained
        // no partial children
        int prevHeightWithoutPartialChild = 0;
        int i;
        View child;

        // mItemCount - 1 since endPosition parameter is inclusive
        endPosition = (endPosition == NO_POSITION) ? adapter.getCount() - 1 : endPosition;
        final AbsListView.RecycleBin recycleBin = mRecycler;
        final boolean recyle = recycleOnMeasure();
        final boolean[] isScrap = mIsScrap;

        for (i = startPosition; i <= endPosition; ++i) {
    
    
            child = obtainView(i, isScrap);

            measureScrapChild(child, i, widthMeasureSpec, maxHeight);

            if (i > 0) {
    
    
                // Count the divider for all but one child
                returnedHeight += dividerHeight;
            }

            // Recycle the view before we possibly return from the method
            if (recyle && recycleBin.shouldRecycleViewType(
                    ((LayoutParams) child.getLayoutParams()).viewType)) {
    
    
                recycleBin.addScrapView(child, -1);
            }

            returnedHeight += child.getMeasuredHeight();

            if (returnedHeight >= maxHeight) {
    
    
                // We went over, figure out which height to return.  If returnedHeight > maxHeight,
                // then the i'th position did not fit completely.
                return (disallowPartialChildPosition >= 0) // Disallowing is enabled (> -1)
                            && (i > disallowPartialChildPosition) // We've past the min pos
                            && (prevHeightWithoutPartialChild > 0) // We have a prev height
                            && (returnedHeight != maxHeight) // i'th child did not fit completely
                        ? prevHeightWithoutPartialChild
                        : maxHeight;
            }

            if ((disallowPartialChildPosition >= 0) && (i >= disallowPartialChildPosition)) {
    
    
                prevHeightWithoutPartialChild = returnedHeight;
            }
        }

        // At this point, we went through the range of children, and they each
        // completely fit, so return the returnedHeight
        return returnedHeight;
    }

源码摘要2

着重注意这几个变量:returnedHeight与dividerHeight。
最终返回的结果是returnedHeight这个变量。
摘录如下源码:
(1)

//这个返回将在列表中的每个项之间绘制的divider的高度。 
 public int getDividerHeight() {
    
    
        return mDividerHeight;
    }

(2)

	//得到ListView的上下padding之和。
   int returnedHeight = mListPadding.top + mListPadding.bottom;
   //得到ListView各项之间的间距
   final int dividerHeight = mDividerHeight;

(3)

//这里的endPosition = adapter.getCount() - 1
 for (i = startPosition; i <= endPosition; ++i) {
    
    
            child = obtainView(i, isScrap);
            //这也是一个ListView中方法,就是得到ListView中的每一个子项(child)。
            measureScrapChild(child, i, widthMeasureSpec, maxHeight);
            //为什么要i>0
            //例如:五项只有四个间隔。即第一项没有间隔。
            if (i > 0) {
    
            
                returnedHeight += dividerHeight;
            }
            //把每一子项的高度加到returnedHeight上
            returnedHeight += child.getMeasuredHeight();
            //只说一句, maxHeight = Integer.MAX_VALUE >> 2,大不大,自己应该也知道吧!所以这里不会执行
            if (returnedHeight >= maxHeight) {
    
    
            .......()
            }
    }

总结

总结:到这里为止,你们知道为什么onMeasure方法要这么写了吧!!!

猜你喜欢

转载自blog.csdn.net/zk2000416/article/details/105007495