Android custom control foundation: MeasureSpec

Preface

MeasureSpec determines the size of a View to a large extent, because this process is also affected by the parent container, because the parent container affects the creation process of View's MeasureSpec. During the measurement process, the system converts the LayoutParams of the View into the corresponding MeasureSpec according to the rules imposed by the parent container, and then measures the final width/height of the View according to the MeasureSpec .

MeasureSpec build

MeasureSpec represents a 32-bit int value, the upper 2 bits represent SpecMode (measurement mode), and the lower 30 bits represent SpcSize (specification size in a certain measurement mode)

源码:
public static class MeasureSpec{
    
    
        private static final int MODE_SHIFT = 30;
        private static final int MODE_MASK  = 0x3 << MODE_SHIFT;

        /** @hide */
        @IntDef({
    
    UNSPECIFIED, EXACTLY, AT_MOST})
        @Retention(RetentionPolicy.SOURCE)
        public @interface MeasureSpecMode {
    
    }

        /**
         * Measure specification mode: The parent has not imposed any constraint
         * on the child. It can be whatever size it wants.
         */
        public static final int UNSPECIFIED = 0 << MODE_SHIFT;

        /**
         * Measure specification mode: The parent has determined an exact size
         * for the child. The child is going to be given those bounds regardless
         * of how big it wants to be.
         */
        public static final int EXACTLY     = 1 << MODE_SHIFT;

        /**
         * Measure specification mode: The child can be as large as it wants up
         * to the specified size.
         */
        public static final int AT_MOST     = 2 << MODE_SHIFT;

        /**
         * Creates a measure specification based on the supplied size and mode.
         *
         * The mode must always be one of the following:
         * <ul>
         *  <li>{@link android.view.View.MeasureSpec#UNSPECIFIED}</li>
         *  <li>{@link android.view.View.MeasureSpec#EXACTLY}</li>
         *  <li>{@link android.view.View.MeasureSpec#AT_MOST}</li>
         * </ul>
         *
         * <p><strong>Note:</strong> On API level 17 and lower, makeMeasureSpec's
         * implementation was such that the order of arguments did not matter
         * and overflow in either value could impact the resulting MeasureSpec.
         * {@link android.widget.RelativeLayout} was affected by this bug.
         * Apps targeting API levels greater than 17 will get the fixed, more strict
         * behavior.</p>
         *
         * @param size the size of the measure specification
         * @param mode the mode of the measure specification
         * @return the measure specification based on size and mode
         */
        public static int makeMeasureSpec(@IntRange(from = 0, to = (1 << MeasureSpec.MODE_SHIFT) - 1) int size,
                                          @MeasureSpecMode int mode) {
    
    
            if (sUseBrokenMakeMeasureSpec) {
    
    
                return size + mode;
            } else {
    
    
                return (size & ~MODE_MASK) | (mode & MODE_MASK);
            }
        }

        /**
         * Like {@link #makeMeasureSpec(int, int)}, but any spec with a mode of UNSPECIFIED
         * will automatically get a size of 0. Older apps expect this.
         *
         * @hide internal use only for compatibility with system widgets and older apps
         */
        @UnsupportedAppUsage
        public static int makeSafeMeasureSpec(int size, int mode) {
    
    
            if (sUseZeroUnspecifiedMeasureSpec && mode == UNSPECIFIED) {
    
    
                return 0;
            }
            return makeMeasureSpec(size, mode);
        }

        /**
         * Extracts the mode from the supplied measure specification.
         *
         * @param measureSpec the measure specification to extract the mode from
         * @return {@link android.view.View.MeasureSpec#UNSPECIFIED},
         *         {@link android.view.View.MeasureSpec#AT_MOST} or
         *         {@link android.view.View.MeasureSpec#EXACTLY}
         */
        @MeasureSpecMode
        public static int getMode(int measureSpec) {
    
    
            //noinspection ResourceType
            return (measureSpec & MODE_MASK);
        }

        /**
         * Extracts the size from the supplied measure specification.
         *
         * @param measureSpec the measure specification to extract the size from
         * @return the size in pixels defined in the supplied measure specification
         */
        public static int getSize(int measureSpec) {
    
    
            return (measureSpec & ~MODE_MASK);
        }

        static int adjust(int measureSpec, int delta) {
    
    
            final int mode = getMode(measureSpec);
            int size = getSize(measureSpec);
            if (mode == UNSPECIFIED) {
    
    
                // No need to adjust size for UNSPECIFIED mode.
                return makeMeasureSpec(size, UNSPECIFIED);
            }
            size += delta;
            if (size < 0) {
    
    
                Log.e(VIEW_LOG_TAG, "MeasureSpec.adjust: new size would be negative! (" + size +
                        ") spec: " + toString(measureSpec) + " delta: " + delta);
                size = 0;
            }
            return makeMeasureSpec(size, mode);
        }

        /**
         * Returns a String representation of the specified measure
         * specification.
         *
         * @param measureSpec the measure specification to convert to a String
         * @return a String with the following format: "MeasureSpec: MODE SIZE"
         */
        public static String toString(int measureSpec) {
    
    
            int mode = getMode(measureSpec);
            int size = getSize(measureSpec);

            StringBuilder sb = new StringBuilder("MeasureSpec: ");

            if (mode == UNSPECIFIED)
                sb.append("UNSPECIFIED ");
            else if (mode == EXACTLY)
                sb.append("EXACTLY ");
            else if (mode == AT_MOST)
                sb.append("AT_MOST ");
            else
                sb.append(mode).append(" ");

            sb.append(size);
            return sb.toString();
        }
    }

Summary: MeasureSpec avoids excessive object memory allocation by packing SpecMode and SpecSize into an int value. SpecMode and SpecSize are an int value, a set of SpecMode and SpecSize can be packed into a MeasureSpec, and a MeasureSpec can be unpacked to get its original SpecMode and SpecSize.

MeasureSpec classification

There are three types of SpecMode:

  • UNSPECIFIED

The parent container does not have any restrictions on the View. It is generally used in the system to indicate a measurement state.

  • EXACTLY

The parent container has detected the exact size required by the View. At this time, the final size of the View is the value specified by SpecSize. It corresponds to the two modes of match_parent and specific values ​​in LayoutParam

  • AT_MOST

The parent container specifies an available size, SpecSize, and the size of the View cannot be greater than this value. The specific value depends on the specific implementation of different Views, which corresponds to the wrap_content in LayoutParam.

Correspondence between MeasureSpec and LayoutParams

The system uses MeasureSpec to measure View, but under normal circumstances, View is used to specify MeasureSpec. During View measurement, the system converts LayoutParams into the corresponding MeasureSpec under the constraints of the parent container, and then determines the measured width/height of the View based on this MeasureSpec.

Note : MeasureSpec is not the only decision by LayoutParams, LayoutParams needs to be together with the parent container to determine the MeasureSpec of the View, which further determines the width/height of the View.


// ViewGroup源码中measureChild 为子View构造MeasureSpec 构建过程
protected void measureChild(View child, int parentWidthMeasureSpec,
         int parentHeightMeasureSpec) {
    
    
     final LayoutParams lp = child.getLayoutParams();

     final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
             mPaddingLeft + mPaddingRight, lp.width);
     final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
             mPaddingTop + mPaddingBottom, lp.height);

     child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
 }
 ......
 // 该方法 根据父容器的MeasureSpec 同时结合子View本身的LayoutParams 来确定子元素的MeasureSpec
 
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
    
    
        int specMode = MeasureSpec.getMode(spec);
        // 父容器大小
        int specSize = MeasureSpec.getSize(spec);
        // padding 父容器中已占用空间大小, size 为子元素可用空间大小
        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);
    }

For top-level View (that is, DecorView) and ordinary View, the conversion process of MeasureSpec is different

  • For DecorView, its MeasureSpec is determined by the size of the window and its own LayoutParams.
  • For ordinary View, its MeasureSpec is jointly determined by the parent container's MeasureSpec and its own LayoutParams.

to sum up

Insert picture description here

  • When the View adopts a fixed width/height, no matter what the parent container MeasureSpec is, the MeasureSpec of the View is the exact mode and the size follows the size in LayoutParams.
  • When the width/height of the View is match_parent, if the mode of the parent container is the precision mode, then the View is also the precision mode and its size is the remaining space of the parent container; if the parent container is the largest mode, then the View is also the largest mode and its size is not Will exceed the remaining space of the parent container.
  • When the width/height of the View is wrap_content, no matter whether the mode of the parent container is accurate or maximized, the mode of the View is always maximized and the size will not exceed the remaining space of the parent container.
  • The UNSPECIFD mode is mainly used in the case of multiple measurements within the system.

Guess you like

Origin blog.csdn.net/xufei5789651/article/details/114234801