美文网首页
LayoutParams, MeasureSpec 与 Meas

LayoutParams, MeasureSpec 与 Meas

作者: Lucien_Liu | 来源:发表于2018-10-26 14:55 被阅读0次

    ViewGroup.LayoutParams 布局参数
    View.MeasureSpec
    View 和 ViewGroup 中几个重要方法分析

    LayoutParams 布局参数

    ViewGroup.LayoutParams 类

    public static class LayoutParams {
            /**
             * Special value for the height or width requested by a View.
             * FILL_PARENT means that the view wants to be as big as its parent,
             * minus the parent's padding, if any. This value is deprecated
             * starting in API Level 8 and replaced by {@link #MATCH_PARENT}.
             */
            @SuppressWarnings({"UnusedDeclaration"})
            @Deprecated
            public static final int FILL_PARENT = -1;
    
            /**
             * 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;
    
            /**
             * Information about how wide the view wants to be. Can be one of the
             * constants FILL_PARENT (replaced by MATCH_PARENT
             * in API Level 8) or WRAP_CONTENT, or an exact size.
             */
            @ViewDebug.ExportedProperty(category = "layout", mapping = {
                @ViewDebug.IntToString(from = MATCH_PARENT, to = "MATCH_PARENT"),
                @ViewDebug.IntToString(from = WRAP_CONTENT, to = "WRAP_CONTENT")
            })
            public int width;
    
            /**
             * Information about how tall the view wants to be. Can be one of the
             * constants FILL_PARENT (replaced by MATCH_PARENT
             * in API Level 8) or WRAP_CONTENT, or an exact size.
             */
            @ViewDebug.ExportedProperty(category = "layout", mapping = {
                @ViewDebug.IntToString(from = MATCH_PARENT, to = "MATCH_PARENT"),
                @ViewDebug.IntToString(from = WRAP_CONTENT, to = "WRAP_CONTENT")
            })
            public int height;
    }
    

    LayoutParams 用于保存 View 的布局属性

    ViewGroup 中定义了 ViewGroup.LayoutParams 作为布局参数的基类,另外在 ViewGroup 中,也定义了 MarginLayoutParams 类,它继承自 ViewGroup.LayoutParams,众多 ViewGroup 的子类中如:
    LinearLayout.LayoutParams,FrameLayout.LayoutParams,RelativeLayout.LayoutParams 都是继承自 ViewGroup.MarginLayoutParams。

    在 ViewGroup.LayoutParams 中定义了 width 和 height 属性,对应布局中的 layout_width 和 layout_height 属性
    在 ViewGroup.MarginLayoutParams 中定义 margin 属性,对应布局中的 layout_margin 和
    layout_marginLeft,layout_marginRight,layout_marginTop,layout_marginRight。

    以 LayoutParams.width 为例,它可能是三种情况 LayoutParams.MATCH_PARENT,LayoutParams.WRAP_CONTENT 或者 一个确切的数值。

    MeasureSpec

    View # MeasureSpec 类

     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;
    
            /**
             * 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);
            }
    

    LayoutParams 和 MeasureSpec 的关系

    LayoutParams 是 View 自身要求的大小,而 MeasureSpec 是由父布局传递给子View的,用以根据父布局自身宽高、padding等,限制子View的宽高。
    widthMeasureSpec 是父布局强加给子View在横向空间上的宽度要求。
    heightMeasureSpec 是父布局强加给子View在纵向空间上的高度要求。

    那么父布局是如何给子View计算得出 widthMeasureSpec 和 heightMeasureSpec 的呢?让我们通过综合对比 FrameLayout 和 LinearLayout 两个 ViewGroup 的子类,
    来彻底弄明白 widthMeasureSpec 和 heightMeasureSpec 的产生原理。

    几个重要方法

    measure 方法

    View # measure(int widthMeasureSpec, int heightMeasureSpec) final 方法,内部调用 onMeasure() 方法
    View # onMeasure(int widthMeasureSpec, int heightMeasureSpec) 直接调用 setMeasuredDimension() 方法,用于设置 View 测量出的宽高
    ViewGroup 中未重写 onMeasure() 方法,由具体的 ViewGroup 子类实现。

    layout 方法

    View # layout(int l, int t, int r, int b) 内部调用 onLayout() 方法
    View # onLayout(boolean changed, int left, int top, int right, int bottom) 空方法体
    ViewGroup # onLayout(boolean changed, int l, int t, int r, int b) 抽象方法,由具体 ViewGroup 的子类实现。

    由上面 measure 和 layout 中涉及到的方法及实现方式可以得出,要实现一个 ViewGroup 的子类,必须要重写 onMeasure() 和 onLayout() 方法。

    在 ViewGroup # measureChildWithMargins() 方法中,可以看出 childWidthMeasureSpec 和 childHeightMeasureSpec 与父容器的 padding 以及自身 LayoutParams 中存放的 margin 有关。

    /**
         * 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);
        }
    

    ViewGroup # getChildMeasureSpec(int spec, int padding, int childDimension) 方法

    /**
         * 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);
        }
    

    相关文章

      网友评论

          本文标题:LayoutParams, MeasureSpec 与 Meas

          本文链接:https://www.haomeiwen.com/subject/plkqtqtx.html