1. ScrollView is nested with ListView. No matter how many items there are, only one item will be displayed.

There are multiple items, but only one item is displayed. Guess there is something wrong with the view measurement.

1.1 View the ListView onMeasure method source code

ListView onMeasure = onMeasure; ListView onMeasure = onMeasure;

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

Here childHeight is the height of an item. Currently, the code logic is optional if the heightMode is MeasureSpec.UNSPECIFIED.

UNSPECIFIED When is heightMode MeasureSpec.UNSPECIFIED?

Currently, It is UNSPECIFIED that MeasureSpec.EXACTLY and MeasureSpec.AT_MOST are both siblings.

  • MeasureSpec.UNSPECIFIEDAs big as you want, internal measurements of the system are rarely needed by developers
  • MeasureSpec.EXACTLYThe layout sets the width and height to match_parent or a specific value
  • MeasureSpec.AT_MOSTLayout setting width and height wrAP_content

As a result, the measurement mode MeasureSpec.UNSPECIFIED is not the result of our XML layout, and we can only set the layout width and height wrAP_content or Match_Parent or specific values in our daily development. UNSPECIFIED, the ListView is still UNSPECIFIED, but the ScrollView layer is still UNSPECIFIED. A ScrollView is a ViewGroup, and a ViewGroup measures its child views as well as itself. The ListView is nested by the ScrollView and displayed as a child of the ScrollView.

1.2 Viewing the source code of the onMeasure method of ScrollView

ScrollView calls super.onMeasure(widthMeasureSpec, heightMeasureSpec) before implementing its own code. Click on the onMeasure method FrameLayout

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    
        / / count to 1
        int count = getChildCount();

        final booleanmeasureMatchParentChildren = MeasureSpec.getMode(widthMeasureSpec) ! = MeasureSpec.EXACTLY || MeasureSpec.getMode(heightMeasureSpec) ! = MeasureSpec.EXACTLY; mMatchParentChildren.clear();int maxHeight = 0;
        int maxWidth = 0;
        int childState = 0;

        // Count is 1, go here
        for (int i = 0; i < count; i++) {
            final View child = getChildAt(i);
            if(mMeasureAllChildren || child.getVisibility() ! = GONE) {// Measure subview (ListView in 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); }}}}... count = mMatchParentChildren.size();// Since a ScrollView can have at most one child View, count is at most 1
        if (count > 1) {... }}Copy the code

In the for loop of FrameLayout’s onMeasure method, the measureChildWithMargins method is called to measure the child view (that is, the ListView in the ScrollView). ScrollView inherits FrameLayout directly, so go to ScrollView and look for the measureChildWithMargins method to see if there’s an overwrite implementation, If anything, the measureChildWithMargins method is the measureChildWithMargins method in the ScrollView, which is a real overwrite

    @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;
        
        // The truth is here
        final int childHeightMeasureSpec = MeasureSpec.makeSafeMeasureSpec(
                Math.max(0, MeasureSpec.getSize(parentHeightMeasureSpec) - usedTotal),
                MeasureSpec.UNSPECIFIED);

        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    }
Copy the code

UNSPECIFIED Mode Is given to MeasureSpec when obtaining childHeightMeasureSpec.UNSPECIFIED Mode. If you’re not sure, follow up with the code for the makeSafeMeasureSpec static method in MeasureSpec

        @UnsupportedAppUsage
        public static int makeSafeMeasureSpec(int size, int mode) {
            if (sUseZeroUnspecifiedMeasureSpec && mode == UNSPECIFIED) {
                return 0;
            }
            return makeMeasureSpec(size, mode);
        }
Copy the code

A MeasureSpec makeSafeMeasureSpec method return calls makeMeasureSpec

        public static int makeMeasureSpec(
        @IntRange(from = 0, to = (1 << MeasureSpec.MODE_SHIFT) - 1) int size,
                                          @MeasureSpecMode int mode) {
            if (sUseBrokenMakeMeasureSpec) {
                return size + mode;
            } else {
                return(size & ~MODE_MASK) | (mode & MODE_MASK); }}Copy the code

MeasureSpec is a size + mode int. ScrollView is a nested ListView.

2. ScrollView nested ListView. No matter how many items there are, only one item will be displayed.

2.1 Overriding the ListView to override the onMeasure method

Any Internet search now turns up a solution:

    public class ListViewOnScrollView extends ListView {...@Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            heightMeasureSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2, MeasureSpec.AT_MOST);
            super.onMeasure(widthMeasureSpec, heightMeasureSpec); }}Copy the code

Inheriting the ListView overrides its onMeasure method to retrieve the onMeasure and replace it with the original ListView.

The problem is solved, but why can other people solve it this way, why write it this way? (Kung Fu practitioners care not only about winning, but also about the subtlety of the moves)

2.2 Why is this resolved?

There should be no doubt why the onMeasure method of ListView should be rewritten. The layout will be measured before display, and the onMeasure method will be used. People are more concerned about why super.onMeasure(widthMeasure, HeightMeasureSpec) before calling heightMeasureSpec = MeasureSpec. MakeMeasureSpec (Integer. MAX_VALUE > > 2, MeasureSpec. AT_MOST); This line of code, why does it work this way?

Again, look at the onMeasure code of the ListView

    @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;
    }
Copy the code

UNSPECIFIED heightMode == MeasureSpec.AT_MOST The heightMode == MeasureSpec. The MeasureHeighto children method is executed

Here, the MeasureSpec.AT_MOST schema is determined

    public class ListViewOnScrollView extends ListView {...@Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            // The MeasureSpec.AT_MOST is passed
            heightMeasureSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2, MeasureSpec.AT_MOST);
            super.onMeasure(widthMeasureSpec, heightMeasureSpec); }}Copy the code

MeasureHeightOfChildren method

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

            // Don't let the code go here
            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;
    }
Copy the code

ReturnedHeight is the measured heights of all items and dividers in the ListView, and the measureHeightOfChildren method return this value cannot make the returnedHeight >= maxHeight condition valid. Int heightSize = MeasureSpec. GetSize (HeightHeight); HeightMeasureSpec is the parameter we need to handle, so in order to ensure that the returnedHeight >= maxHeight condition is not set, let the maxHeight get the maximum intege.MAX_VALUE, So we can override the ListView’s onMeasure method when we’re dealing with this problem

    public class ListViewOnScrollView extends ListView {...@Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            // The MeasureSpec.AT_MOST is passed
            // Also determines the incoming parameter integer.max_value
            heightMeasureSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2, MeasureSpec.AT_MOST);
            super.onMeasure(widthMeasureSpec, heightMeasureSpec); }}Copy the code

Integer.MAX_VALUE >> 2 shifts the value of Integer.MAX_VALUE >> 2 shifts the value of Integer.

This requires additional analysis in conjunction with MeasureSpec. We looked at the code for MeasureSpec’s makeMeasureSpec method when we looked at why things went wrong

        public static int makeMeasureSpec(
        @IntRange(from = 0, to = (1 << MeasureSpec.MODE_SHIFT) - 1) int size,
                                          @MeasureSpecMode int mode) {
            if (sUseBrokenMakeMeasureSpec) {
                return size + mode;
            } else {
                return(size & ~MODE_MASK) | (mode & MODE_MASK); }}Copy the code

Can clear know heightMeasureSpec = MeasureSpec. MakeMeasureSpec (Integer. MAX_VALUE > > 2, MeasureSpec. AT_MOST); The heightMeasureSpec contains size and mode information

The values of MeasureSpec.UNSPECIFIED, MeasureSpec.EXACTLY, and MeasureSpec.AT_MOST are as follows

    public static class MeasureSpec {
        private static final int MODE_SHIFT = 30; .public static final int UNSPECIFIED = 0 << MODE_SHIFT;
        
        public static final int EXACTLY     = 1 << MODE_SHIFT;

        public static final int AT_MOST     = 2<< MODE_SHIFT; . }Copy the code

For example, MeasureSpec.AT_MOST is 2 << MODE_SHIFT, which means that the binary value of 2 is shifted 30 bits to the left

In Java, int is stored in the range of 32 bits, the binary value of 0 is [first 30 zeros]00, the binary value of 1 is [first 30 zeros] 0, and the binary value of 1 is [first 30 zeros]10

Integer.MAX_VALUE >> 2; Integer.MAX_VALUE >> 2; Integer

HeightMeasureSpec = Mode Size Binary position0 1   2-31
Copy the code

ScrollView nested ListView can display only one item

    public class ListViewOnScrollView extends ListView {...@Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            heightMeasureSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2, MeasureSpec.AT_MOST);
            super.onMeasure(widthMeasureSpec, heightMeasureSpec); }}Copy the code