It’s your birthday. There was an accident

That day, the wind was fine and the sun was shining. Xiao Zhang, an Android development engineer, was happily writing the bug code. Suddenly, Lao Shi, the test manager, ran over and said that the customer reported an online bug. Then Shi pulled the phone out of his crotch pocket and reproduced the bug:

Scene:

As you can see clearly from the picture, the service frame at the bottom is blocked by cutting, and the display is incomplete.

Analyze the reasons:

Seeing the restored crime scene, Xiao Zhang felt relieved: “It’s ok, it’s ok, I didn’t write the code.” In front of Lao Shi, Xiao Zhang admitted that there was a problem with the code, promised to fix the next version of Lao Shi, Lao Shi didn’t bother to scratch away. With Lao Shi dismissed, Xiao Zhang began to analyze the problem. At first, Xiao Zhang simply thought that the height of the bottom Dialog was too small, causing the contents of the Dialog display to be incomplete. However, goose, see the problem of the code, xiao Zhang began to find that things are not simple, involved in the code is as follows:

<? xml version="1.0" encoding="utf-8"? > <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"> <! , omitting irrelevant code - > < com. Xx. ListViewForScrollView android: layout_width ="match_parent"
        android:layout_height="match_parent"
        android:layout_marginLeft="20dp"
        android:layout_marginTop="18dip"
        android:layout_marginRight="20dp"
        android:layout_marginBottom="48dp"
        android:divider="@null">
    </com.xx.ListViewForScrollView>
</RelativeLayout>
Copy the code

Look at ListViewForScrollView in the layout XML file of Dialog above, and you can’t help but wonder: “Why is this a custom View?” The ListViewForScrollView should be able to slide even if the height of the Dialog is insufficient. The ListViewForScrollView should not be able to slide.

The ListViewForScrollView is not a custom View. The ListViewForScrollView is not a custom View. The ListViewForScrollView is a custom View.

public class ListViewForScrollView extends ListView { public ListViewForScrollView(Context context) { super(context); } public ListViewForScrollView(Context context, AttributeSet attrs) { super(context, attrs); } public ListViewForScrollView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); } @override /** * Override this method, */ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int expandSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2, MeasureSpec.AT_MOST); super.onMeasure(widthMeasureSpec, expandSpec); }}Copy the code

“It’s an inherited ListView. There’s no special logic to it. Why can’t it slide?” In order to solve the in the mind of doubt, zhang decided to from the ListViewForScrollView the only way to override onMeasure (int widthMeasureSpec, int heightMeasureSpec), see I can found the problem. MeasureSpec (MeasureSpec) is a MeasureSpec (MeasureSpec). The MeasureSpec can’t be solved if you don’t understand the MeasureSpec, so zhang decides to check out the MeasureSpec:

Xiao Zhang wants to say something about MeasureSpec

As we all know, View display on mobile phone needs to go through three processes: measurement – drawing – layout. The corresponding View code is onMeasure(), onDraw(), onLayout() three methods. In the onMeasure() process, we need to measure the View using MeasureSpec. The onMeasure() method takes two parameters, widthMeasureSpec and heightMeasureSpec, both of which are 32-bit ints passed by the View’s parent control. The onMeasure() method of the View is called by the parent control. The size of the View is determined only by the parent control. It’s not. As we said earlier, widthMeasureSpec and heightMeasureSpec are both 32-bit ints, with the highest two bits representing the measure mode and the lowest 30 bits representing the measure size. The MeasureSpec static class makes it easy to get the measurement mode and size of the View. There are three measurement modes:

  • The parent control has no restrictions on you, and it is given to you as much as you want. This situation is generally used within a system to indicate a state of measurement. (This mode is mainly used for multiple measures inside the system, and it does not really mean that the size you want is really the size)

  • EXACTLY the parent control already knows the exact size you want. Your final size should be this size.

  • AT_MOST You must not be larger than the size given to you by your parent control, but how much is up to your implementation.

The parent control obtains the child control’s measurement specification through the static method getChildMeasureSpec in the ViewGroup. GetChildMeasureSpec () = childMeasurespec () = ChildMeasurespec ();

 /**
     * Does the hard part of measureChildren: figuring out the MeasureSpec to
     * pass to a particular child. This method figures out the right MeasureSpec
     * for one dimension (height or width) of one child view.
     *
     * The goal is to combine information from our MeasureSpec with the
     * LayoutParams of the child to get the best possible results. For example,
     * if the this view knows its size (because its MeasureSpec has a mode of
     * EXACTLY), and the child has indicated in its LayoutParams that it wants
     * to be the same size as the parent, the parent should ask the child to
     * layout given an exact size.
     *
     * @param spec The requirements for this view
     * @param padding The padding of this view for the current dimension and
     *        margins, if applicable
     * @param childDimension How big the child wants to be in the current
     *        dimension
     * @return a MeasureSpec integer for the child
     */
    public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
        int specMode = MeasureSpec.getMode(spec);
        int specSize = MeasureSpec.getSize(spec);

        int size = Math.max(0, specSize - padding);

        int resultSize = 0;
        int resultMode = 0;

        switch (specMode) {
        // Parent has imposed an exact size on us
        case MeasureSpec.EXACTLY:
            if (childDimension >= 0) {
                resultSize = childDimension;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.MATCH_PARENT) {
                // Child wants to be our size. So be it.
                resultSize = size;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                // Child wants to determine its own size. It can't be // bigger than us. resultSize = size; resultMode = MeasureSpec.AT_MOST; } break; // Parent has imposed a maximum size on us case MeasureSpec.AT_MOST: if (childDimension >= 0) { // Child wants a specific size... so be it resultSize = childDimension; resultMode = MeasureSpec.EXACTLY; } else if (childDimension == LayoutParams.MATCH_PARENT) { // Child wants to be our size, but our size is not fixed. // Constrain child to not be bigger than us. resultSize = size; resultMode = MeasureSpec.AT_MOST; } else if (childDimension == LayoutParams.WRAP_CONTENT) { // Child wants to determine its own size. It can't be
                // bigger than us.
                resultSize = size;
                resultMode = MeasureSpec.AT_MOST;
            }
            break;

        // Parent asked to see how big we want to be
        case MeasureSpec.UNSPECIFIED:
            if (childDimension >= 0) {
                // Child wants a specific size... let him have it
                resultSize = childDimension;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.MATCH_PARENT) {
                // Child wants to be our size... find out how big it should
                // be
                resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
                resultMode = MeasureSpec.UNSPECIFIED;
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                // Child wants to determine its own size.... find out how
                // big it should be
                resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
                resultMode = MeasureSpec.UNSPECIFIED;
            }
            break;
        }
        //noinspection ResourceType
        return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
    }
Copy the code

This method combines the measurement specifications of the parent control with the layout parameters LayoutParams of the ChildView to obtain a child View measurement specification that is most likely to meet the criteria. This code gives you the idea that the size of a View is determined by its parent control plus its own LayoutParams. The details are as follows (pictures from Ren Yugang’s blog) :

To solve the problem

After understanding the Measure process of View, Xiao Zhang knows that in the onMeasure() method of ListViewForScrollView, the Measure mode of ListView is forcibly changed to AT_MOST. Change the size of the measure to MAX_VALUE >> 2, and then call the onMeasure() method of ListView. Let’s look at the onMeasure() method of ListView:

/** Override protected void onMeasure(int widthMeasureSpec, int widthMeasureSpec, int widthMeasureSpec) Int heightMeasureSpec) {/ * * * * here omit unrelated code / / /... // The measurement mode is AT_MOST and the measurement size is MAX_VALUE >> 2if (heightMode == MeasureSpec.AT_MOST) {
            heightSize = measureHeightOfChildren(widthMeasureSpec, 0, NO_POSITION, heightSize, -1);
        }

        setMeasuredDimension(widthSize, heightSize); /** Continue omit **/ //...... }Copy the code

As you can see from the code above, the final height of this custom ListViewForScrollView is set by the measureHeightOfChildren method, which zhang then highlighted:

/**
     * Measures the height of the given range of children (inclusive) and
     * returns the height with this ListView's padding and divider heights * included. If maxHeight is provided, the measuring will stop when the * current height reaches maxHeight. * * @param widthMeasureSpec The width measure spec to be given to a child's
     *            {@link View#measure(int, int)}.* @param startPosition The position of the first child to be shown. * @param endPosition The (inclusive) position of the  last child to be * shown. Specify {@link#NO_POSITION} if the last child should be
     *            the last available child from the adapter.
     * @param maxHeight The maximum height that will be returned (if all the
     *            children don't fit in this value, this value will be * returned). * @param disallowPartialChildPosition In general, whether the returned * height should only contain entire children. This is more * powerful--it is the first inclusive position at which partial * children will not be allowed. Example: it looks nice to have * at least 3 completely visible children, and in portrait this * will most likely fit; but in landscape there could be times * when even 2 children can not be completely shown, so a value * of 2 (remember, inclusive) would be good (assuming * startPosition is 0). * @return The height of this ListView with the given children.  */ 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;
    }
Copy the code

By looking at this method, Xiao Zhang knows that the meaning of this method is to measure the height of a given range and return the height containing the ListView’s padding and separator height. If maxHeight is provided, the measurement will stop when the current height reaches maxHeight. For ListViewForScrollView, maxHeight is 536870911(MAX_VALUE >> 2) from onMeasure(). If the total height of ListView itemView does not exceed this value, So the ListView height is the height of all the ItemViews plus the delimiter plus the padding, otherwise it’s 536870911. So the reason for the problem is clear. The ListViewForScrollView height in most cases is 536870911, and the ListView cannot slide.

To solve the problem

After researching the ListViewForScrollView, Zhang discovered that this custom View is designed to solve the sliding conflicts caused by ScrollView nesting ListView. The Dialog root layout in question is a RelativeLayout layout, not a ScrollView. So Zhang replaced the ListViewForScrollView in the layout file with a ListView, and the problem was solved.

A fall into the pit, a gain in your wit

Through this lesson of blood and tears, Xiao Zhang understood:

  1. View drawing process
  2. The role of the MeasureSpec
  3. When using a custom View, understand what it does