View those things (2) -- Understanding MeasureSpec

Keywords: Java Windows xml SDK

Measure is the first step in the process of drawing View. To understand the measurement process of View, we must first understand MeasureSpec. Literally, MeasureSpec means "measurement specification". In fact, it determines the measurement process of View to a certain extent. Specifically, it contains the dimension information of a View. When the system measures the size specifications of View, the Layout Params of View are converted into corresponding MeasureSpec according to the rules imposed by the parent container, and then the width of View is measured according to MeasureSpec.

I. Composition of MeasureSpec

MeasureSpec represents a 32-bit int, a high 2-bit SpecMode and a low 30-bit SpecSize. The following analysis is combined with a section of source code:

public static class MeasureSpec {
    private static final int MODE_SHIFT = 30;//Left-shifted digits of binary numbers
    private static final int MODE_MASK  = 0x3 << MODE_SHIFT;//Move the binary number 11 left by 30 bits
    public static final int UNSPECIFIED = 0 << MODE_SHIFT;//Move the binary number 00 left by 30 bits          
    public static final int EXACTLY= 1 << MODE_SHIFT;//Move the binary number 01 left by 30 bits
    public static final int AT_MOST= 2 << MODE_SHIFT;//Move the binary number 10 to the left by 30 bits  
    //Package SpecMode and SpecSize as MeasureSpec
    public static int makeMeasureSpec(int size, int mode ){
        if (sUseBrokenMakeMeasureSpec) {
            return size + mode;
        } else {
            return (size & ~MODE_MASK) | (mode & MODE_MASK);
        }
    }
    //Extract SpecMode from mesureSpec
    public static int getMode(int measureSpec) {
        return (measureSpec & MODE_MASK);
    }
    //Remove SpecSize from measureSpec
    public static int getSize(int measureSpec) {
        return (measureSpec & ~MODE_MASK);
    }
}

Through the above source code, we can clearly see the working principle of the whole MeasureSpec, which involves bit-by-bit operation in java, "&" means bit-by-bit operation, "~" means bit-by-bit inversion, "<<" is a left-shift operator (x < 1 means the value of X multiplied by 2). These operators are all operated on the level of binary numbers, and the specific operation rule goo There's a whole bunch of gles on it, so I won't say much here.
Three constants of the MeasrureSpec class: UNSPECIFIED, EXACTLY and AT_MOST represent three SpecMode (measurement mode):

  • UNSPECIFIED (not specified size)
    The parent container does not have any restrictions on View and how big it wants to be. This situation generally belongs to the system itself, indicating a measurement state, which is almost unnecessary. For example, the system draws ScrollView.
  • EXACTLY (Accurate)
    The parent container has detected the exact size required by the View, at which point the final size of the View is the value specified by SpecSize. This corresponds to match_parent in LayoutParams and specific values, such as 20dp.
  • AT_MOST (Largest)
    This pattern is slightly more difficult to handle. It means that the parent container specifies a usable size (SpecSize) that cannot exceed the size of the View. If it exceeds, the size of the parent container is taken, and if it does not exceed, the size of the parent container is taken. This corresponds to the wrap_content pattern in LayoutParams.

It's all theory. No demo. Let's go straight to the last section of code.

First, the effect is simple: a circular custom View (we mainly deal with data in the case of wrap_content, we have to give it a default value):

public class CircleView extends View {
    Paint mPaint;//cpen
    public CircleView(Context context) {
        super(context);
    }

    public CircleView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public CircleView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
    public CircleView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

        //The first step must be to get SpecSize and SpecMode of View.
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);
        //Actual Size of View Display
        int width = 0;
        int height = 0;
        //Start processing width
        //Default width (there must be a default width in the case of wrap_content)
        int defaultWidth = 200;
        //SpecMode (SpecMode of View is determined when the width of View is specified in xml)
        switch (widthMode){
            case MeasureSpec.AT_MOST://This refers to wrap_content, which in this mode cannot exceed the width of the parent container.
                width = defaultWidth;
                break;
            case MeasureSpec.EXACTLY://This means match_parent or a specific value. There is no need to do anything about it. width is directly equal to widthSize.
                width = widthSize;
                break;
            case MeasureSpec.UNSPECIFIED://This model is not needed and can be completely ignored.
                width = defaultWidth;
                break;
            default:
                width = defaultWidth;
                break;
        }
        //Start processing height
        int defaultHeight = 200;
        switch (heightMode){
            case MeasureSpec.AT_MOST://This refers to wrap_content, which in this mode cannot exceed the height of the parent container.
                height = defaultHeight;
                break;
            case MeasureSpec.EXACTLY://This means match_parent or a specific value. There is no need to do anything about it. The height is directly equal to the height Size.
                height = heightSize;
                break;
            case MeasureSpec.UNSPECIFIED://This model is not needed and can be completely ignored.
                height = defaultHeight;
                break;
            default:
                height = defaultHeight;
                break;
        }
        //Finally, we must call the measurement method of the parent class to save the width and height of our calculation so that the set measurement values will take effect.
        setMeasuredDimension(width,height);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        //Initialize the brush and set up a series of settings, such as color.
        mPaint = new Paint();
        mPaint.setColor(Color.RED);
        mPaint.setAntiAlias(true);//Setting Anti-aliasing
        //The parameters of canvas.drawCircle are x coordinate, y coordinate, radius and brush.
        canvas.drawCircle(getWidth()/2,getHeight()/2,Math.min(getWidth()/2,getHeight()/2),mPaint);
    }
}

The results are as follows:

You can try it out. If you don't do the above processing in onMreasure (you can see the effect by annotating the onMeasure method), the wrap_content set for CircleView will fail. The actual display effect is no different from match_parent.
Write a custom View in advance here to help readers understand the relevant usage of MeasureSpec and combine it in detail. Previous article It's easy for you to understand the explanations and the code annotations.
Of course, in order to express this process clearly, I write the code in detail, which seems a bit cumbersome, but in fact, it can not be so detailed.

The relationship between MeasureSpec and LayouParams

It's all about MeasureSpec. Now it's time to talk about how he came from.
There are two parts here: the first is the ordinary View, the second is DecorView.

  • In the ordinary view measurement process, the Layout Params of the View itself will be transformed into the corresponding MeasureSpec under the constraint of the parent container, and then the width and height of the View measurement will be determined according to the MeasureSpec.
  • During the measurement of DecorView (top-level View), the LayoutParams generated by View will be transformed into corresponding MeasureSpec under the constraint of window size.

1. First of all, let's focus on the ordinary View.

For a normal View (that is, View in layout text), its measurement process is passed from ViewGroup, so let's first look at the measureChildWithMargins() method of ViewGroup:

//Method of Measuring Sub-Views of ViewGroup
protected void measureChildWithMargins(View child,
            int parentWidthMeasureSpec, int widthUsed,
              int parentHeightMeasureSpec, int heightUsed) {
    //Get layout parameter information for the child View (Margin Layout Params is inherited from ViewGroup. Layout Parmas)
    final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
    //To get MeasureSpec of the width of the child View, you need to pass in parentWidthMeasureSpec and other information from the parent container.
    final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
            mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin
                    + widthUsed, lp.width);
    //To get the height of MeasureSpec of the child View, you need to pass in information such as parent HeightMeasureSpec of the parent container.    
    final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
            mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin
                    + heightUsed, lp.height);

    child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}  

Combined with the annotations, it can be seen from the source code of the MesureSpec of the measurement child View above that the MesureSpec information of the parent container and LayoutParams information of the child View itself (such as margin and padding) are passed in the measurement, so it is said that the measurement process of the ordinary View is related to the parent container and its own LayoutParams.
So, as you know the specific process of measurement, you have to look at getChildMeasureSpec () as a method (it seems to have a lot of code, but it's very simple):

public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
          //Or get specMode and specSize information first
        int specMode = MeasureSpec.getMode(spec);
        int specSize = MeasureSpec.getSize(spec);
          //Padding refers to the size of space already occupied by the parent container, so the size of the child View is subtracted from the size of the parent container by padding.
        int size = Math.max(0, specSize - padding);
          //The final specSize and specMode of View after measurement
        int resultSize = 0;
        int resultMode = 0;
          //Judgment for three specMode s
        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);
    }

The above code clearly shows the measurement process, and the following table combs the specific judgment logic. (parentSize refers to the size of parent container currently available)

parentSpecMode →
childLayoutParams↓
EXACTLY AT_MOST UNSPICIFIED
Dp/px (determined width/height) EXACTLY
childSize
EXACTLY
childSize
EXACTLY
childSize
match_parent EXACTLY
parentSize
AT_MOST
parentSize
UNSPECIFIED
0
wrap_content AT_MOST
parentSize
AT_MOST
parentSize
UNSPECIFIED
0

According to the table, the judgment rules of the getChildMeasureSpec() method are clear at a glance:

  • When View specifies a certain width and height, whatever the SpecMode of the parent container is, its SpecMode is EXACTLY, and the size follows the size in LayoutParams.
  • When the width/height of View is specified as match_parent, its SpecMode is the same as the parent container, and its size cannot exceed the size of the remaining space of the parent container.
  • When the width/height of the View is specified as wrap_content, its SpecMode is constant (regardless of UNSPECIFIED) AT_MOST, and the size cannot exceed the size of the remaining space of the parent container.

Since the UNSPECIFIED model is generally out of reach, we will not discuss it here.
From the above summary, it is easy to understand and remember the relationship between Layout Params of MesureSpec in ordinary View.

2. Let's take a look at Decor View.

For DecorView, MeasureSpec is determined by window size and its own LayoutParams.
Let's look at the source code. There's a meaureHierarchy() method in ViewRootImpl (this class is hidden, and you need to search the sdk directory manually to find the source code in ViewRootImpl.java). It has the following code:

childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth, lp.width);  
childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);  
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);      

Among them, desired Windows Width and desired Windows Height refer to the width of the screen, respectively.
Seeing this, you can feel that DecorView's MesureSpec has something to do with window size. Looking at getRoot MeasureSpec, you can see that:

private int getRootMeasureSpec(int windowSize, int rootDimension) {  
    int measureSpec;  
    switch (rootDimension) {  

    case ViewGroup.LayoutParams.MATCH_PARENT:  
        // Window can't resize. Force root view to be windowSize.  
        measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);  
        break;  
    case ViewGroup.LayoutParams.WRAP_CONTENT:  
        // Window can resize. Set max size for root view.  
        measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);  
        break;  
    default:  
        // Window wants to be an exact size. Force root view to be that size.  
        measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);  
        break;  
    }  
    return measureSpec;  
}    

Seeing this, it's clear that DecorView's MesureSpec does have something to do with window size.

Therefore, Decor's MesureSpec follows the following rules according to its Layout Params:

  • LayoutParams. MATCH_PARENT: EXACTLY (Precision Mode), the size is the size of the window;
  • LayoutParams. WRAP_CONTENT: AT_MOST (maximum mode), size is variable, but can not exceed the size of the window;
  • Fixed size (e.g. 200dp): EXACTLY (precision mode), size set by LayoutParams.

At this point, the understanding of MeasureSpec is over.

Posted by pedrokas on Sun, 02 Jun 2019 16:25:54 -0700