View drawing series:

  • DecorView and ViewRootImpl for Android View drawing process

  • Android View drawing process Measure process details (1)

  • Android View drawing process Layout and Draw process details (2)

  • Android View event distribution principle analysis

 

An overview of the

The previous article in DecorView and ViewRootImpl analyzed how a DecorView is associated with an activity after calling setContentView. Finally, I looked at the logic to start drawing in view rule PL. This article continues with the previous article and begins to analyze the drawing process of view.

PerformTraversals render render render render render render render render render render render render render render render render render render render render render render

// Private void performTraversals() { PerformMeasure (Child Wide Measure, Child Tall Measure); PerformLayout (lp, mWidth, mHeight); // Execute draw performDraw(); }Copy the code

The process is as follows:

The entire drawing process of a View can be divided into the following three stages:

  • Measure: Determine whether the View size needs to be recalculated or calculated if necessary.
  • Layout: Determine whether the position of the View needs to be recalculated, if necessary;
  • Draw: Determines whether the View needs to be redrawn or redrawn if necessary.

MeasureSpec

MeasureSpec before I introduce you to drawing, take a look at MeasureSpec. MeasureSpec encapsulates the layout requirements passed from parent to child, and is represented by a 32-bit int value that contains two bits of information: the higher two bits are SpecMode, and the lower 30 bits are SpecSize. The following is a comment to analyze the class:

UNSPECIFIED * The parent ViewGroup imposes no constraints on its child views, which can be of any size. This kind of situation is relatively rare, mainly used in the case of multiple measures inside the system. * is generally used in the child view in the container that can be rolled, such as ListView, GridView, RecyclerView in some cases. * In general, we don't need to worry about this pattern. * 2.EXACTLY * The view must use the size specified by the parent ViewGroup. Corresponding to match_parent or a specific value (such as 30dp) * 3.AT_MOST * This View can take the maximum size specified by the parent ViewGroup. The wrAP_CONTENT * * MeasureSpec counterpart uses binary to reduce object allocation. */ public class MeasureSpec {// The size of a MeasureSpec is 32 bits. Private static final int MODE_SHIFT = 30; private static final int MODE_SHIFT = 30; // Operation mask, 0x3 is hexadecimal, 10 is 3, and binary is 11. 3 carries 30 to the left, which is 11 00000000000(11 followed by 30 zeros) // (The function of the mask is to mark the desired value with 1 and the unwanted value with 0. Private static final int MODE_MASK = 0x3 << MODE_SHIFT; // 0 carries 30 bits, which is 00 00000000000(00 is followed by 30 0s) 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; // measureSpec = size + mode; // measureSpec = size + mode; (Note: binary addition, not decimal addition!) // As size=100(4), mode=AT_MOST, then measureSpec=100+10000... 00 = 10000.. 00100 // // second return: // size &; ~MODE_MASK is the last 30 bits of size, mode &amp; MODE_MASK is the number obtained by taking the first two digits of mode and performing the last or operation. The first two digits contain the number representing mode. Public static int makeMeasureSpec(int size, int mode) { if (sUseBrokenMakeMeasureSpec) { return size + mode; } else { return (size & ~MODE_MASK) | (mode & MODE_MASK); // mode = measureSpec &amp; MODE_MASK; // MODE_MASK = 11 00000000000(11 followed by 30 zeros); // MODE_MASK = 11 00000000000(11 followed by 30 zeros); // MODE_MASK = 11 00000000000(11 followed by 30 zeros); // For example 10 00.. 00 00100 & 11.. 00(11 followed by 30 zeros) = 10 00.. Public static int measureSpec (measureSpec) {return (measureSpec & MODE_MASK); // / size = measureSpec & ~MODE_MASK; // MODE_MASK = 00 111111; // MODE_MASK = 00 111111; Size public static int measureSpec (measureSpec) {return (measureSpec & ~MODE_MASK); }}Copy the code

By the way, MATCH_PARENT and WRAP_CONTENT are -1 and -2, respectively.

       /**
         * Special value for the height or width requested by a View.
         * MATCH_PARENT means that the view wants to be as big as its parent,
         * minus the parent's padding, if any. Introduced in API Level 8.
         */
        public static final int MATCH_PARENT = -1;

        /**
         * Special value for the height or width requested by a View.
         * WRAP_CONTENT means that the view wants to be just large enough to fit
         * its own internal content, taking its own padding into account.
         */
        public static final int WRAP_CONTENT = -2;
Copy the code

Decorview size determination

In formTraversals, the first step is to determine the size of the DecorView. Only when the DecorView size is determined can its children know how big they can be. To determine this, see the following code:

//Activity window width and height int desiredWindowWidth; int desiredWindowHeight; . Rect frame = mWinFrame; Rect frame = mWinFrame; If (mFirst) {// If (mFirst) {// If (mFirst) {mFullRedrawNeeded = true; // Whether you need to redetermine Layout mLayoutRequested = true; // There are two cases: // Check whether the window to draw contains the status bar, if yes, remove it. Then determine the height and width of the Decorview to draw if (shouldUseDisplaySize(lp)) {// NOTE -- system code won't try to do compat mode. Point size = new Point(); mDisplay.getRealSize(size); desiredWindowWidth = size.x; desiredWindowHeight = size.y; } else {// Width and height are values for the entire screen. Configuration config = McOntext.getresources ().getConfiguration(); desiredWindowWidth = dipToPx(config.screenWidthDp); desiredWindowHeight = dipToPx(config.screenHeightDp); }... Else {// This is the case where the length and width of the window have changed, and the changes need to be recorded. // If it is not the first time to use this method, its current width and height are obtained from the previous mWinFrame. desiredWindowHeight = frame.height(); /** * if (desiredWindowWidth! = mWidth || desiredWindowHeight ! = mHeight) { if (DEBUG_ORIENTATION) Log.v(mTag, "View " + host + " resized to: " + frame); // Full redraw needed to fit the new window size mFullRedrawNeeded = true; // Need to rearrange the control tree mLayoutRequested = true; // Window window size change windowSizeMayChange = true; }}... // Take a predicate if (layoutRequested){... If (mFirst) {// Whether the view window is currently in touch mode. mAttachInfo.mInTouchMode = ! mAddedTouchMode; // Make sure the Window's touch mode is set to ensureTouchModeLocally(mAddedTouchMode); } else {// Six if statements, determine the changes in the IMAGE value from the last time, change the insetsChanged // the IMAGE value includes some reserved areas of the screen, record some blocked areas and other information if (! mPendingOverscanInsets.equals(mAttachInfo.mOverscanInsets)) { insetsChanged = true; }... // There is a case where we add the layout manually while writing the dialog. When we set the width to Wrap_content, we assign the width and height of the screen, If the width or height of the current window's root layout is specified as WRAP_CONTENT, such as Dialog, then we still give it the maximum width and width. Here is the screen width * / if assigned to it (lp. Width = = ViewGroup. LayoutParams. WRAP_CONTENT | | lp. The height = = ViewGroup. LayoutParams. WRAP_CONTENT)  { windowSizeMayChange = true; // Check whether the window to draw contains the status bar. Then determine the height and width of the Decorview to draw if (shouldUseDisplaySize(lp)) {// NOTE -- system code won't try to do compat mode. Point size = new Point(); mDisplay.getRealSize(size); desiredWindowWidth = size.x; desiredWindowHeight = size.y; } else { Configuration config = res.getConfiguration(); desiredWindowWidth = dipToPx(config.screenWidthDp); desiredWindowHeight = dipToPx(config.screenHeightDp); } } } } }Copy the code

There are two main steps:

  1. If you are measuring for the first time, use the height of the screen directly or the actual height of the display area depending on whether there is a status bar.
  2. If it is not the first time, get it from mWinFrame and compare it with the previously saved length, width and height. If it is not the same, you need to measure again to determine the height.

Once the DecorView’s exact size is determined, measureHierarchy is then called to determine its MeasureSpec:

 // Ask host how big it wants to be
  windowSizeMayChange |= measureHierarchy(host, lp, res,
        desiredWindowWidth, desiredWindowHeight); 
Copy the code

Host = host; lp = host = host; lp = host;

private boolean measureHierarchy(final View host, final WindowManager.LayoutParams lp, final Resources res, final int desiredWindowWidth, final int desiredWindowHeight) { int childWidthMeasureSpec; int childHeightMeasureSpec; boolean windowSizeMayChange = false; A Boolean called goodMeasure = false; / / is a dialog if (lp. Width = = ViewGroup. LayoutParams. WRAP_CONTENT) {/ / On large screens, we don't want to allow dialogs to just // stretch to fill the entire width of the screen to display // one line of text.  First try doing the layout at a smaller // size to see if it will fit. final DisplayMetrics packageMetrics = res.getDisplayMetrics(); res.getValue(com.android.internal.R.dimen.config_prefDialogWidth, mTmpValue, true); int baseSize = 0; Type == typedValue.type_Dimension) {baseSize = (int) mtmpValue.getDimension (packageMetrics); baseSize = (int) mtmpValue.getDimension (packageMetrics); } if (DEBUG_DIALOG) Log.v(mTag, "Window " + mView + ": BaseSize =" + baseSize + ", desiredWindowWidth=" + desiredWindowWidth); // If (baseSize! = 0 && desiredWindowWidth > baseSize) { childWidthMeasureSpec = getRootMeasureSpec(baseSize, lp.width); childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height); performMeasure(childWidthMeasureSpec, childHeightMeasureSpec); if (DEBUG_DIALOG) Log.v(mTag, "Window " + mView + ": measured (" + host.getMeasuredWidth() + "," + host.getMeasuredHeight() + ") from width spec: " + MeasureSpec.toString(childWidthMeasureSpec) + " and height spec: "+ MeasureSpec. ToString (childHeightMeasureSpec)); / / whether measuring accurately the if ((host. GetMeasuredWidthAndState () & the MEASURED_STATE_TOO_SMALL) = = 0) {called goodMeasure = true; } else { // Didn't fit in that size... try expanding a bit. baseSize = (baseSize+desiredWindowWidth)/2; if (DEBUG_DIALOG) Log.v(mTag, "Window " + mView + ": next baseSize=" + baseSize); childWidthMeasureSpec = getRootMeasureSpec(baseSize, lp.width); performMeasure(childWidthMeasureSpec, childHeightMeasureSpec); if (DEBUG_DIALOG) Log.v(mTag, "Window " + mView + ": measured (" + host.getMeasuredWidth() + "," + host.getMeasuredHeight() + ")"); if ((host.getMeasuredWidthAndState()&View.MEASURED_STATE_TOO_SMALL) == 0) { if (DEBUG_DIALOG) Log.v(mTag, "Good!" ); goodMeasure = true; }}}} // Here is the general DecorView logic if (! goodMeasure) { childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth, lp.width); childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height); performMeasure(childWidthMeasureSpec, childHeightMeasureSpec); If (mWidth! = host.getMeasuredWidth() || mHeight ! = host.getMeasuredHeight()) { windowSizeMayChange = true; } } return windowSizeMayChange; }Copy the code

The parent View’s MeasureSpec is the parent View’s MeasureSpec. But there are two different types:

  • If the width is WRAP_CONTENT, it is a dialog, and there will be some processing for the dialog, which will eventually be measured by calling performMeasure;
  • For a normal Activity size, getRootMeasureSpec MeasureSpec is called.

DecorView MeasureSpec = MeasureSpec = MeasureSpec = MeasureSpec = MeasureSpec = MeasureSpec

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

The View’s MeasureSpec is divided by the width and height parameters.

  • MATCH_PARENT: exact mode, the size is the size of the window;
  • WRAP_CONTENT: maximum mode, variable size, but no larger than the window size;
  • Fixed size: Precise mode, size is specified specific width and height, such as 100dp.

Go is the first case for DecorView, DecorView MeasureSpec is established here, can be obtained from MeasureSpec DecorView wide high constraints information.

Gets the child view’s MeasureSpec

When the parent ViewGroup measures the child View, it calls the View class’s measure method, which is final and cannot be overridden. The ViewGroup passes its own widthMeasureSpec and heightMeasureSpec, which indicate some restrictions on the parent View’s width and height, respectively. Especially if the ViewGroup is WRAP_CONTENT, we need to measure the subview first. Only when the width and height of the subview are determined, the ViewGroup can determine how much width and height it needs.

When the DecorView’s MeasureSpec is determined, the ViewRootImpl internally calls the performMeasure method:

private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) { if (mView == null) { return; } Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure"); try { mView.measure(childWidthMeasureSpec, childHeightMeasureSpec); } finally { Trace.traceEnd(Trace.TRACE_TAG_VIEW); }}Copy the code

MeasureSpec = MeasureSpec = MeasureSpec = MeasureSpec = MeasureSpec = MeasureSpec = MeasureSpec = Measure ()

/** * Call this method to figure out how big a View should be. Parameter is the constraint information on the width and height of the parent View. Public final void measure(int widthMeasureSpec, int heightMeasureSpec) {......Copy the code
  // Suppress sign extension for the low bytes
   long key = (long) widthMeasureSpec << 32 | (long) heightMeasureSpec & 0xffffffffL;
   if (mMeasureCache == null) mMeasureCache = new LongSparseLongArray(2);
Copy the code
// If mPrivateFlags contains PFLAG_FORCE_LAYOUT, Forcing a relayout such as view.requestLayout () will add this flag to mPrivateFlags final Boolean forceLayout = (mPrivateFlags &) PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT; final boolean specChanged = widthMeasureSpec ! = mOldWidthMeasureSpec || heightMeasureSpec ! = mOldHeightMeasureSpec; final boolean isSpecExactly = MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY && MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.EXACTLY; final boolean matchesSpecSize = getMeasuredWidth() == MeasureSpec.getSize(widthMeasureSpec) && getMeasuredHeight() == MeasureSpec.getSize(heightMeasureSpec); final boolean needsLayout = specChanged && (sAlwaysRemeasureExactly || ! isSpecExactly || ! matchesSpecSize); / / need to layout the if (forceLayout | | needsLayout) {Copy the code
MPrivateFlags &= ~PFLAG_MEASURED_DIMENSION_SET; / / such as Arabic, Hebrew writing from right to left, the layout of the language of the special processing resolveRtlPropertiesIfNeeded ();Copy the code
Int cacheIndex = forceLayout int cacheIndex = forceLayout int cacheIndex = forceLayout -1 : mMeasureCache.indexOfKey(key); if (cacheIndex < 0 || sIgnoreMeasureCache) { // measure ourselves, this should set the measured dimension flag back onMeasure(widthMeasureSpec, heightMeasureSpec); } else {mMeasureCache = mMeasureCache. ValueAt (cacheIndex); // Casting a long to int drops the high 32 bits, no mask needed setMeasuredDimensionRaw((int) (value >> 32), (int) value); mPrivateFlags3 |= PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;Copy the code
    }
Copy the code
// If the custom View overrides the onMeasure method, but does not call setMeasuredDimension(), an error will be raised here;Copy the code
// flag not set, setMeasuredDimension() was not invoked, We raise // an exception to warn the developer if ((mPrivateFlags & PFLAG_MEASURED_DIMENSION_SET)! = PFLAG_MEASURED_DIMENSION_SET) {throw new IllegalStateException("View with ID "+ getId() + ": " + getClass().getName() + "#onMeasure() did not set the" + " measured dimension by calling" + " SetMeasuredDimension () "); }Copy the code

// At this point, the View is finished measuring and stores the measurement results in mMeasuredWidth and mMeasuredHeight of the View, marking the position as layoutable

mPrivateFlags |= PFLAG_LAYOUT_REQUIRED;

} mOldWidthMeasureSpec = widthMeasureSpec; mOldHeightMeasureSpec = heightMeasureSpec; / / save in the cache mMeasureCache. Put (key, ((long) mMeasuredWidth) < < 32 | (long) mMeasuredHeight & 0 XFFFFFFFFL); // suppress sign extension }Copy the code

The important thing to note here is that this is a final method and cannot be inherited. This method is only in the View class. To summarize what Measure () has done:

  • callView.measure()The View does not measure immediately, but decides whether to measure first. If it is not necessary, then the View does not need to measure again to avoid wasting time and resources
  • If you need to measure, before measuring, it will check whether there is cache. If there is cache, it will fetch it directly from cachesetMeasuredDimensionRaw Method to save the measurements read from the cache to a member variablemMeasuredWidth andmMeasuredHeight In the.
  • If you can’t getmMeasureCache To read the cached measurement resultsonMeasure() Method to complete the actual measurement work, and size constraintswidthMeasureSpec andheightMeasureSpec Passed to theonMeasure() Methods. aboutonMeasure() Methods are described in more detail below.
  • Save the result tomMeasuredWidth andmMeasuredHeight Both member variables are cached to member variablesmMeasureCache To be executed next timemeasure() Method from which to read the cache value.
  • It should be noted that the View has a member variablemPrivateFlagsIs used to save various status bits of View. Before the measurement starts, it will be set to the unmeasured state, and after the measurement is completed, it will be set to the measured state.

DecorView is a FrameLayout subclass and should look at the onMeasure() method in FrameLayout:

Protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {int childCount = getChildCount(); final boolean measureMatchParentChildren = 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); / / mMeasureAllChildren defaults to FALSE, the son said whether to measure all children view, the view is not GONE to measure the if (mMeasureAllChildren | | child. The getVisibility ()! = GONE) {// measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0); Final LayoutParams lp = (LayoutParams) child.getLayOutParams (); 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()); / / record all with parent layout with the same width or height of the child view the if (measureMatchParentChildren) {if (lp) width = = LayoutParams) MATCH_PARENT | | lp. Height = = LayoutParams.MATCH_PARENT) { mMatchParentChildren.add(child); }}}} // Account for padding too Plus the parent View their own padding maxWidth + = getPaddingLeftWithForeground () + getPaddingRightWithForeground (); maxHeight += getPaddingTopWithForeground() + getPaddingBottomWithForeground(); // Check against our minimum height and width maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight()); maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth()); // Check against our foreground's minimum height and width final Drawable drawable = getForeground(); if (drawable ! = null) { maxHeight = Math.max(maxHeight, drawable.getMinimumHeight()); maxWidth = Math.max(maxWidth, drawable.getMinimumWidth()); } setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState), resolveSizeAndState(maxHeight, heightMeasureSpec, childState << MEASURED_HEIGHT_STATE_SHIFT)); count = mMatchParentChildren.size(); if (count > 1) { for (int i = 0; i < count; i++) { final View child = mMatchParentChildren.get(i); final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams(); final int childWidthMeasureSpec; // If the width of the child view is MATCH_PARENT, then the width = parent view width - parent padding-child Margin if (lp.width == layoutparams.match_parent) {final int width = Math.max(0, getMeasuredWidth() - getPaddingLeftWithForeground() - getPaddingRightWithForeground() - lp.leftMargin - lp.rightMargin);  childWidthMeasureSpec = MeasureSpec.makeMeasureSpec( width, MeasureSpec.EXACTLY); } else { childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec, getPaddingLeftWithForeground() + getPaddingRightWithForeground() + lp.leftMargin + lp.rightMargin, lp.width); } final int childHeightMeasureSpec; // If (lp.height == layoutparams.match_parent) {final int height = math.max (0, getMeasuredHeight() - getPaddingTopWithForeground() - getPaddingBottomWithForeground() - lp.topMargin - lp.bottomMargin); childHeightMeasureSpec = MeasureSpec.makeMeasureSpec( height, MeasureSpec.EXACTLY); } else { childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec, getPaddingTopWithForeground() + getPaddingBottomWithForeground() + lp.topMargin + lp.bottomMargin, lp.height); } child.measure(childWidthMeasureSpec, childHeightMeasureSpec); }}}Copy the code

FrameLayout is a subclass of ViewGroup that has a member variable of type View[], mChildren, that represents its child View collection. GetChildAt (I) gets the child views at the specified index, and getChildCount() gets the total number of child Views.

In the above source, the measureChildWithMargins() method is called to measure all the sub views and calculate the maximum width and height of the sub views. Then add the padding to the resulting maximum height and width, which includes the padding of the parent View and the padding of the foreground area. It then checks if the minimum width is set and compares it, setting the larger of the two to the final maximum width. Finally, if the foreground image is set, we also check the minimum width and height of the foreground image.

After the above steps, we have the final values of maxHeight and maxWidth, which indicate that the current container View will display all its child views properly at this size (both padding and margin are taken into account). Then we need to call the resolveSizeAndState() method in combination with the MeasureSpec passed to get the final measurement width and height, stored in the mMeasuredWidth and mMeasuredHeight member variables.

If there are some child views whose width or height is MATCH_PARENT, then recalculate the child View’s MeasureSpec after the parent View’s size is calculated, and then measure the child View’s width and height.

Here remind us in the custom View when you need to consider whether your child View and the size of your custom View is the same, if the same, you need to wait for the size of the custom View is determined, and then measure again.

MeasureChildWithMargins ()

/** * Ask one of the children of this view to measure itself, taking into * account both the MeasureSpec requirements for this view and its padding * and margins. The child must have  MarginLayoutParams The heavy lifting is * done in getChildMeasureSpec. * * @param child The child to measure * @param parentWidthMeasureSpec The width requirements for this view * @param widthUsed Extra space that has been used up by the parent * horizontally (possibly by other children of the parent) * @param parentHeightMeasureSpec The height requirements for this view * @param heightUsed Extra space that has been used up by the parent * vertically (possibly by  other children of the parent) */ 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 childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec, mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin + heightUsed, lp.height); child.measure(childWidthMeasureSpec, childHeightMeasureSpec); }Copy the code

MeasureSpec = MeasureSpec = MeasureSpec = MeasureSpec = MeasureSpec = MeasureSpec = MeasureSpec = MeasureSpec MeasureSpec = MeasureSpec = MeasureSpec = MeasureSpec

public static int getChildMeasureSpec(int spec, int padding, Int childDimension) {// Parent view's mode and size int specMode = MeasureSpec. Int specSize = MeasureSpec. GetSize (spec); // remove the padding int size = math.max (0, specsie-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 clearly shows the creation rules for a Common View’s MeasureSpec, Each View’s MeasureSpec state is determined by both its parent’s MeasureSpec and the View’s own property, LayoutParams, which has dimensions such as width and height.

MeasureSpec = MeasureSpec = MeasureSpec = MeasureSpec = MeasureSpec = MeasureSpec

  • The child View asSpecific width/height, so the View’s MeasureSpec is the size of LayoutParams.
  • The child View asmatch_parentWhen the parent is EXACTLY, the View’s MeasureSpec is EXACTLY the size of the parent container.
  • The child View aswrap_contentA View’s MeasureSpec is always in maximized mode and does not exceed the size of the remaining space of the parent container, regardless of whether the parent element is in exact mode or maximized mode (AT_MOST).
  • The parent container is UNSPECIFIED, which is mainly used in the case of multiple system measures. Generally, the parent container is UNSPECIFIED.

It is summarized in the following table:

OnMeasuere () : view.onMeasuere () : onMeasuere() : view.onMeasuere () : onMeasuere() : onMeasuere();

 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
                getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
    }
Copy the code

The above method calls the setMeasuredDimension() method, and setMeasuredDimension() calls getDefaultSize(). GetDefaultSize again call getSuggestedMinimumWidth () () and getSuggestedMinimumHeight (), the reverse study, To see the first getSuggestedMinimumWidth () method (getSuggestedMinimumHeight () principle getSuggestedMinimumWidth () with the same).

 protected int getSuggestedMinimumWidth() {
        return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
    }
Copy the code

The source code is very simple, if the View does not have a background, directly return the View itself the minimum width of mMinWidth; If you have a background for the View, take the minimum width of the View itself, mMinWidth, and the maximum minimum width of the background.

So where does mMinWidth come from? The mMinWidth of a View can be set in two ways:

  • The first is assigned in the constructor of the View, which is set by reading the View in the XML fileminWidth Attribute tomMinWidth Assignment:
case R.styleable.View_minWidth:
     mMinWidth = a.getDimensionPixelSize(attr, 0);
     break;
Copy the code
  • The second is calling the ViewsetMinimumWidth Methods formMinWidth The assignment
public void setMinimumWidth(int minWidth) {
    mMinWidth = minWidth;
    requestLayout();
}
Copy the code

Let’s look at the code logic for getDefaultSize() :

    public static int getDefaultSize(int size, int measureSpec) {
        int result = size;
        int specMode = MeasureSpec.getMode(measureSpec);
        int specSize = MeasureSpec.getSize(measureSpec);

        switch (specMode) {
        case MeasureSpec.UNSPECIFIED:
            result = size;
            break;
        case MeasureSpec.AT_MOST:
        case MeasureSpec.EXACTLY:
            result = specSize;
            break;
        }
        return result;
    }
Copy the code

As you can see from the comments, the getDefaultSize() measure does not match wrap_content, but simply equates wrap_content with match_parent.

At this point, we should pay attention to a question:

getDefaultSize()In the methodwrap_content andmatch_parent Property has the same effect, and the method is ViewonMeasure()That is, for a custom View that inherits directly from the View, its wrap_content and match_parent properties are the same, so if you want to implement a custom View’swrap_content, should be rewrittenonMeasure() Methods,wrap_content Property for processing.

How do you deal with that? Also very simple, the code looks like this:

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec){ super.onMeasure(widthMeasureSpec, heightMeasureSpec); Int widthSpecMode = MeasureSpec. GetMode (widthMeasureSpec); int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec); int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec); int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec); If (widthSpecMode == MeasureSpec.AT_MOST &&heightSpecMode == MeasureSpec.AT_MOST) {// If (widthSpecMode == MeasureSpec.AT_MOST &&heightSpecMode == MeasureSpec. SetMeasuredDimension (mWidth, mHeight) to wrap_content; setMeasuredDimension(mWidth, mHeight) to wrap_content; }else if (widthSpecMode == MeasureSpec.AT_MOST) {// If (widthSpecMode == MeasureSpec.AT_MOST) {// If (widthSpecMode == MeasureSpec. SetMeasuredDimension (mWidth, heightSpecSize); }else if (heightSpecMode == measurespec.at_most) {// If (heightSpecMode == measurespec.at_most) { SetMeasuredDimension (widthSpecSize, mHeight); }}Copy the code

In the above code, we specify a default internal width/height (mWidth and mHeight) for the View and set this width/height at wrAP_content. Finally, set the width and height to the View:

// View protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) { boolean optical = isLayoutModeOptical(this); if (optical ! = isLayoutModeOptical(mParent)) { Insets insets = getOpticalInsets(); int opticalWidth = insets.left + insets.right; int opticalHeight = insets.top + insets.bottom; measuredWidth += optical ? opticalWidth : -opticalWidth; measuredHeight += optical ? opticalHeight : -opticalHeight; } setMeasuredDimensionRaw(measuredWidth, measuredHeight); } private void setMeasuredDimensionRaw(int measuredWidth, int measuredHeight) { mMeasuredWidth = measuredWidth; mMeasuredHeight = measuredHeight; mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET; }Copy the code

Assign the measured width and height to the mMeasuredWidth and mMeasuredHeight views, and mark the position as measured.

After the child View is measured, it calculates the childState. Look at the combineMeasuredStates method:

public static int combineMeasuredStates(int curState, int newState) {
        return curState | newState;
    }
Copy the code

If curState is 0, newState is obtained by calling child-getMeasuredState (). If curState is 0, newState is obtained by calling child-getMeasuredState ().

/** * Return only the state bits of {@link #getMeasuredWidthAndState()} * and {@link #getMeasuredHeightAndState()}, combined into one integer. * The width component is in the regular bits {@link #MEASURED_STATE_MASK} * and the height component is at the shifted bits * {@link #MEASURED_HEIGHT_STATE_SHIFT}>>{@link #MEASURED_STATE_MASK}. */ public final int getMeasuredState() { return (mMeasuredWidth&MEASURED_STATE_MASK) | ((mMeasuredHeight>>MEASURED_HEIGHT_STATE_SHIFT) &  (MEASURED_STATE_MASK>>MEASURED_HEIGHT_STATE_SHIFT)); }Copy the code

This method returns an int that contains the state of the width and the state of the height, but does not contain any size information.

  • MEASURED_STATE_MASK has a value of 0xFF000000, with all 8 bits of the high byte being 1 and all 24 bits of the low byte being 0.
  • The MEASURED_HEIGHT_STATE_SHIFT value is 16.
  • willMEASURED_STATE_MASK 与 mMeasuredWidth After doing the and operation, we extract the state information stored in the first byte of the width and filter out the size information of the lower three bytes.
  • Since int has four bytes, the state of the width is stored in the first byte, so the height isstateThe message cannot have the first byte.MEASURED_STATE_MASKIf we move 16 places to the right, it becomes 1, 2, 30x0000ff00, this value and the height valuemMeasuredHeight Do and operate and take it outmMeasuredHeight Information in the third byte. whilemMeasuredHeight The state information is in the first byte, so it also has to be correctmMeasuredHeight Move the state information to the third byte by the same amount to the right.
  • Finally, the resulting width state and height state are bitwise or manipulated, so that an int value is concatenated, the first byte of which stores the state information of the width and the third byte stores the state information of the height.

With that in mind, we can calculate the size of the parent View:

SetMeasuredDimension (resolveSizeAndState(maxWidth, widthMeasureSpec, childState), resolveSizeAndState(maxHeight, heightMeasureSpec, childState << MEASURED_HEIGHT_STATE_SHIFT));Copy the code

ResolveSizeAndState:

Public static int resolveSizeAndState(int size, int measureSpec, int childMeasuredState) { final int specMode = MeasureSpec.getMode(measureSpec); final int specSize = MeasureSpec.getSize(measureSpec); final int result; switch (specMode) { case MeasureSpec.AT_MOST: if (specSize < size) { result = specSize | MEASURED_STATE_TOO_SMALL; } else { result = size; } break; case MeasureSpec.EXACTLY: result = specSize; break; case MeasureSpec.UNSPECIFIED: default: result = size; } return result | (childMeasuredState & MEASURED_STATE_MASK); }Copy the code

The code structure of this method is similar to the getDefaultSize() method mentioned above, with the main difference being that the specMode is AT_MOST. We said that the getDefaultSize() method does not fit wrap_content, and the resolveSizeAndState() method does. How to achieve AT_MOST measurement logic? There are two cases:

  • The maximum size specified by the parent ViewGroup is given when the size specified by the parent ViewGroup is smaller than the size desired by the ViewspecSize Add a small MEASURED_STATE_TOO_SMALL flag, and return the MEASURED_STATE_TOO_SMALL flag. The parent ViewGroup can use this flag to determine the size of the View when negotiating measurements.
  • When the maximum size specified by the parent ViewGroup is not smaller than the size desired by the View (equal to or less than the size desired by the View), it simply takes the size desired by the View and returns that size.

The getDefaultSize() method is the default implementation of the onMeasure() method to get the final size. It returns less information than the resolveSizeAndState() method. When will the resolveSizeAndState() method be called? There are two main cases:

  • Most of the ViewGroup classes in Android are calledresolveSizeAndState() Methods such as LinearLayout are called during measurementresolveSizeAndState() Methods rather thangetDefaultSize()Methods.
  • When we implement our own custom View or ViewGroup, we can rewrite itonMeasure() Method and is called within that methodresolveSizeAndState() Methods.

Here, finally the View measurement process is finished.

The next chapter starts with the process of View layout and draw.

 

Refer to the article

Android source code fully parse – View Measure process

View drawing process