As an Android developer, under normal circumstances, the View drawing mechanism is basically familiar, especially for the students who often need to customize the View to achieve some special effects.

Online also appeared a large number of Blog about View onMeasure(), onLayout(), onDraw() and so on, although this is a every Android development should know things, but this series is too much, completely does not meet our short and quick this series of original intention.

So, today we are going to talk briefly about MeasureSpec, which is very important in the Measure () process.

As most people know, MeasureSpec is a 32-bit int. The first two digits represent Mode and the last 30 digits represent Size.

MeasureSpec has three modes, which are EXACTLY, AT_MOST, and UNSPECIFIED.

  • MeasureSpec.EXACTLY: In this mode, the size of the component is EXACTLY the same as the width and length of the componentMATCH_PARENTAnd the determined value.
  • (MeasureSpec.AT_MOST) : This is the size of the parent component. The size of the current component can be as large or as small as this. The correspondingWRAP_CONETNT.
  • UNSPECIFIED mode (MeasureSpec.UNSPECIFIED) : This means that the current component has UNSPECIFIED space.

In general, when we are customizing a View, we often have to deal with AT_MOST and EXACTLY, we usually define our View size according to the two modes, and use a default value calculated or set by ourselves for wrap_content. Most of the time, it is assumed that the UNSPECIFIED mode is applied to the system source code. This is embodied in NestedScrollView and ScrollView.

Let’s look at an XML file like this:

<?xml version="1.0" encoding="utf-8"? >
<android.support.v4.widget.NestedScrollView xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/scrollView"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <TextView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@color/colorAccent"
        android:text="Hello World"
        android:textColor="#fff">
    </TextView>

</android.support.v4.widget.NestedScrollView>
Copy the code

Inside the NestedScrollView we wrote a TextView full of screen height. To make it easier to see the effect, we set a background color. But we were surprised to see something different in the XML preview.

We expected a TextView to fill the screen, but the effect is exactly the same as setting the height of the TextView to wrAP_content.

Obviously, this must be a height measurement problem, if our parent layout is LinearLayout, obviously there is no problem. So the problem must be in the onMeasure() of NestedScrollView.

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    if (this.mFillViewport) {
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        if(heightMode ! =0) {
            if (this.getChildCount() > 0) {
                View child = this.getChildAt(0);
                LayoutParams lp = (LayoutParams)child.getLayoutParams();
                int childSize = child.getMeasuredHeight();
                int parentSpace = this.getMeasuredHeight() - this.getPaddingTop() - this.getPaddingBottom() - lp.topMargin - lp.bottomMargin;
                if (childSize < parentSpace) {
                    int childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec, this.getPaddingLeft() + this.getPaddingRight() + lp.leftMargin + lp.rightMargin, lp.width);
                    int childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(parentSpace, 1073741824);
                    child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
                }
            }

        }
    }
}
Copy the code

Since we’re not setting mFillViewport outside, we’re not going to go into the if condition, so let’s see what happens to the onMeasure() of NestedScrollView’s super FrameLayout.

@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) { 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); }}}}// ignore something...
}
Copy the code

Note the key method, measureChildWithMargins(), which is completely overwritten in NestedScrollView.

protected void measureChildWithMargins(View child, int parentWidthMeasureSpec, int widthUsed, int parentHeightMeasureSpec, int heightUsed) {
    MarginLayoutParams lp = (MarginLayoutParams)child.getLayoutParams();
    int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec, this.getPaddingLeft() + this.getPaddingRight() + lp.leftMargin + lp.rightMargin + widthUsed, lp.width);
    int childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(lp.topMargin + lp.bottomMargin, 0);
    child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}	
Copy the code

We see a key line in the code:

int childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(lp.topMargin + lp.bottomMargin, 0);
Copy the code

NestedScrollView ignores the user-set MODE and uses UNSPECIFIED. After testing, we found that when we overwrote the NestedScrollView line and set MODE to EXACTLY, we got what we wanted. I have checked Google’s source submission log and found no reason.

In fact, this is why most of the nested ListViews or RecylerViews encountered before development only show one line, The solution is to override measureChildWithMargins() on NestedScrollView or the onMeasure() method on ListView or RecylerView to display the correct height.

Originally, I assumed that the scrolling would only be UNSPECIFIED, but unfortunately this is not the case. So here’s the question, hoping someone in the know can discuss it together.