I explained the mechanism of event distribution in the previous article, but I will continue with a real-world scenario where a ScrollView is nested with a ListView that can display only one item.

Analysis:

The onMeasure() method is the same as the ScrollView method:

 @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    // ScrollView inherits from FrameLayout, so FrameLayout's onMeasure() method is implemented
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    //mFillViewport defaults to false
        if(! mFillViewport) {return; }...Copy the code

Step by step, FrameLayout’s onMeasure():

 @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        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;

        for (int i = 0; i < count; i++) {
            final View child = getChildAt(i);
            if(mMeasureAllChildren || child.getVisibility() ! = GONE) {// The most important step is to measure the child view, and the method scrollView has been overridden
                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); }}}}...Copy the code

The measureChildWithMargins() method is important, and srCollView is overwritten, so look again:

@Override
    protected void measureChildWithMargins(View child, int parentWidthMeasureSpec, int widthUsed,
            int parentHeightMeasureSpec, int heightUsed) {
        final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
// ListView wide mode
        final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
                mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin
                        + widthUsed, lp.width);
        final int usedTotal = mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin +
                heightUsed;
UNSPECIFIED, the ListView's mode is still UNSPECIFIED. This is the primary reason for limiting the ListView to one item.
        final int childHeightMeasureSpec = MeasureSpec.makeSafeMeasureSpec(
                Math.max(0, MeasureSpec.getSize(parentHeightMeasureSpec) - usedTotal),
                MeasureSpec.UNSPECIFIED);
// Measure child.
//childWidthMeasureSpec, childWidthMeasureSpec is the listView measurement mode
        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    }Copy the code

Currently, the ScrollView forcibly sets the height mode of its ChildView as UNSPECIFIED. Now, the ListView calculates the height of its ChildView with UNSPECIFIED height.

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

In this mode, the height of the ListView is equal to the vertical padding+ the height of the first child +?? Does not affect judgment. The reason why scrollView nested Listivew results show only one item is clear.

Summary: ScrollView sets childView to UNSPEFEIED mode by default, and the height measured by the ListView in this mode is the height of the first item.

1. Set mFillViewport to true to see the full onMeasure() code of ScrollView:

 @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
// If mFillViewport is true, the following code can be executed.
        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);
// The measurement mode is EXACTLY
                final int childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(
                        desiredHeight, MeasureSpec.EXACTLY);
// observe the measurement mode of childHeightMeasureSpecchild.measure(childWidthMeasureSpec, childHeightMeasureSpec); }}}Copy the code

If the following code can be executed, the ListView will be displayed in full. That is, setting the value of mFillViewport to true will solve the problem. You can add fillViewport = true to the scrollView layout; It can also be set dynamically.

2. Customize ListView: The purpose of the source code analysis is to clear the cause of the problem, so as to be able to put forward targeted solutions, listView only shows one is also because of their own problems in the onMeasure() measurement, after all, is the listView returns an item height to the ScrollView, Scrollview displays the height of an item. So we can also customize the Listview and rewrite the onMeasure() method to return the normal height to the ScrollView. A custom ListView simply overrides the onMeasure() method:

@Override
//>>2 moves right two places because the first 2 of MeasureSpec is the mode and the last 30 bits are the size
        int customHeightSpec = 
        MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2,MeasureSpec.AT_MOST);
        super.onMeasure(widthMeasureSpec, customHeightSpec);
    }Copy the code

The LinearLayout is set to UNSPECFIED mode, and the ListView must set the exact DP value.

As shown in the figure below, the linearLayout is UNSPECIFIED, and the listview must be an exact dp to avoid being set to UNSPECIFIED mode.

There is no mechanism for more event distribution. In the next article I’ll look at the problems with ListView nesting Scrollview to better understand the event distribution mechanism.

Thanks for reading!