Margin的优先级

1,244 阅读2分钟

在我们配置布局时候,会出现layout_margin,layout_marginTop这样的参数。
当然为了防止歧义,一般用了layout_marginTop就不会用layout_margin了,但如果它们一同出现,会出现什么化学反应呢

分析布局

我们看下在ConstraintLayout里面是什么表现

<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content">
    <TextView
        android:background="@color/colorPrimary"
        android:layout_width="200dp"
        android:layout_height="200dp"
        android:layout_margin="20dp"
        android:layout_marginHorizontal="80dp"
        android:layout_marginStart="80dp"
        android:layout_marginLeft="80dp"
        android:layout_marginTop="20dp"
        android:layout_marginVertical="20dp"
        android:layout_gravity="top|left"

        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

img
可以看出间距在20dp左右,故layout_margin优先级最高


我们把layout_margin去掉

<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <TextView
        android:layout_width="200dp"
        android:layout_height="200dp"
        android:layout_marginHorizontal="200dp"
        android:layout_marginStart="20dp"
        android:layout_marginLeft="80dp"
        android:background="@color/colorPrimary"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

img
可以得知layout_marginStart优先级次高


然后我们去掉layout_marginStart

<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <TextView
        android:layout_width="200dp"
        android:layout_height="200dp"
        android:layout_marginHorizontal="200dp"
        android:layout_marginLeft="80dp"
        android:background="@color/colorPrimary"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

img

得知layout_marginHorizontal >layout_marginLeft


垂直方向同理

<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <TextView
        android:layout_width="200dp"
        android:layout_height="200dp"
        android:layout_marginVertical="200dp"
        android:layout_marginTop="80dp"
        android:background="@color/colorPrimary"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

img/mar

得出结论

于是可推断
layout_margin > layout_marginStart > layout_marginHorizontal>layout_marginLeft
layout_margin > layout_marginVertical > layout_marginTop

分析源码

既然知其然,就要知其所以然,我们不妨看下ViewGroup的源码,ViewGroup里面有个MarginLayoutParams类

(版本API30)

/**
 * Per-child layout information for layouts that support margins.
 * See
 * {@link android.R.styleable#ViewGroup_MarginLayout ViewGroup Margin Layout Attributes}
 * for a list of all child view attributes that this class supports.
 *
 * @attr ref android.R.styleable#ViewGroup_MarginLayout_layout_margin
 * @attr ref android.R.styleable#ViewGroup_MarginLayout_layout_marginHorizontal
 * @attr ref android.R.styleable#ViewGroup_MarginLayout_layout_marginVertical
 * @attr ref android.R.styleable#ViewGroup_MarginLayout_layout_marginLeft
 * @attr ref android.R.styleable#ViewGroup_MarginLayout_layout_marginTop
 * @attr ref android.R.styleable#ViewGroup_MarginLayout_layout_marginRight
 * @attr ref android.R.styleable#ViewGroup_MarginLayout_layout_marginBottom
 * @attr ref android.R.styleable#ViewGroup_MarginLayout_layout_marginStart
 * @attr ref android.R.styleable#ViewGroup_MarginLayout_layout_marginEnd
 */
public static class MarginLayoutParams extends ViewGroup.LayoutParams {
    /**
     * The left margin in pixels of the child. Margin values should be positive.
     * Call {@link ViewGroup#setLayoutParams(LayoutParams)} after reassigning a new value
     * to this field.
     */
    @ViewDebug.ExportedProperty(category = "layout")
    @InspectableProperty(name = "layout_marginLeft")
    public int leftMargin;

    @ViewDebug.ExportedProperty(category = "layout")
    @InspectableProperty(name = "layout_marginTop")
    public int topMargin;

    @ViewDebug.ExportedProperty(category = "layout")
    @InspectableProperty(name = "layout_marginRight")
    public int rightMargin;

    @ViewDebug.ExportedProperty(category = "layout")
    @InspectableProperty(name = "layout_marginBottom")
    public int bottomMargin;

    /**
     * Creates a new set of layout parameters. The values are extracted from
     * the supplied attributes set and context.
     *
     * @param c     the application environment
     * @param attrs the set of attributes from which to extract the layout
     *              parameters' values
     */
    public MarginLayoutParams(Context c, AttributeSet attrs) {
        super();

        TypedArray a = c.obtainStyledAttributes(attrs, R.styleable.ViewGroup_MarginLayout);
        setBaseAttributes(a,
                R.styleable.ViewGroup_MarginLayout_layout_width,
                R.styleable.ViewGroup_MarginLayout_layout_height);

        int margin = a.getDimensionPixelSize(
                com.android.internal.R.styleable.ViewGroup_MarginLayout_layout_margin, -1);
        if (margin >= 0) {
            leftMargin = margin;
            topMargin = margin;
            rightMargin = margin;
            bottomMargin = margin;
        } else {
            int horizontalMargin = a.getDimensionPixelSize(
                    R.styleable.ViewGroup_MarginLayout_layout_marginHorizontal, -1);
            int verticalMargin = a.getDimensionPixelSize(
                    R.styleable.ViewGroup_MarginLayout_layout_marginVertical, -1);

            if (horizontalMargin >= 0) {
                leftMargin = horizontalMargin;
                rightMargin = horizontalMargin;
            } else {
                leftMargin = a.getDimensionPixelSize(
                        R.styleable.ViewGroup_MarginLayout_layout_marginLeft,
                        UNDEFINED_MARGIN);
                if (leftMargin == UNDEFINED_MARGIN) {
                    mMarginFlags |= LEFT_MARGIN_UNDEFINED_MASK;
                    leftMargin = DEFAULT_MARGIN_RESOLVED;
                }
                rightMargin = a.getDimensionPixelSize(
                        R.styleable.ViewGroup_MarginLayout_layout_marginRight,
                        UNDEFINED_MARGIN);
                if (rightMargin == UNDEFINED_MARGIN) {
                    mMarginFlags |= RIGHT_MARGIN_UNDEFINED_MASK;
                    rightMargin = DEFAULT_MARGIN_RESOLVED;
                }
            }

            startMargin = a.getDimensionPixelSize(
                    R.styleable.ViewGroup_MarginLayout_layout_marginStart,
                    DEFAULT_MARGIN_RELATIVE);
            endMargin = a.getDimensionPixelSize(
                    R.styleable.ViewGroup_MarginLayout_layout_marginEnd,
                    DEFAULT_MARGIN_RELATIVE);

            if (verticalMargin >= 0) {
                topMargin = verticalMargin;
                bottomMargin = verticalMargin;
            } else {
                topMargin = a.getDimensionPixelSize(
                        R.styleable.ViewGroup_MarginLayout_layout_marginTop,
                        DEFAULT_MARGIN_RESOLVED);
                bottomMargin = a.getDimensionPixelSize(
                        R.styleable.ViewGroup_MarginLayout_layout_marginBottom,
                        DEFAULT_MARGIN_RESOLVED);
            }

            if (isMarginRelative()) {
                mMarginFlags |= NEED_RESOLUTION_MASK;
            }
        }

        final boolean hasRtlSupport = c.getApplicationInfo().hasRtlSupport();
        final int targetSdkVersion = c.getApplicationInfo().targetSdkVersion;
        if (targetSdkVersion < JELLY_BEAN_MR1 || !hasRtlSupport) {
            mMarginFlags |= RTL_COMPATIBILITY_MODE_MASK;
        }

        // Layout direction is LTR by default
        mMarginFlags |= LAYOUT_DIRECTION_LTR;

        a.recycle();
    }

    ....
    private void doResolveMargins() {
        if ((mMarginFlags & RTL_COMPATIBILITY_MODE_MASK) == RTL_COMPATIBILITY_MODE_MASK) {
            // if left or right margins are not defined and if we have some start or end margin
            // defined then use those start and end margins.
            if ((mMarginFlags & LEFT_MARGIN_UNDEFINED_MASK) == LEFT_MARGIN_UNDEFINED_MASK
                    && startMargin > DEFAULT_MARGIN_RELATIVE) {
                leftMargin = startMargin;
            }
            if ((mMarginFlags & RIGHT_MARGIN_UNDEFINED_MASK) == RIGHT_MARGIN_UNDEFINED_MASK
                    && endMargin > DEFAULT_MARGIN_RELATIVE) {
                rightMargin = endMargin;
            }
        } else {
            // We have some relative margins (either the start one or the end one or both). So use
            // them and override what has been defined for left and right margins. If either start
            // or end margin is not defined, just set it to default "0".
            switch(mMarginFlags & LAYOUT_DIRECTION_MASK) {
                case View.LAYOUT_DIRECTION_RTL:
                    leftMargin = (endMargin > DEFAULT_MARGIN_RELATIVE) ?
                            endMargin : DEFAULT_MARGIN_RESOLVED;
                    rightMargin = (startMargin > DEFAULT_MARGIN_RELATIVE) ?
                            startMargin : DEFAULT_MARGIN_RESOLVED;
                    break;
                case View.LAYOUT_DIRECTION_LTR:
                default:
                    leftMargin = (startMargin > DEFAULT_MARGIN_RELATIVE) ?
                            startMargin : DEFAULT_MARGIN_RESOLVED;
                    rightMargin = (endMargin > DEFAULT_MARGIN_RELATIVE) ?
                            endMargin : DEFAULT_MARGIN_RESOLVED;
                    break;
            }
        }
        mMarginFlags &= ~NEED_RESOLUTION_MASK;
    }
}

首先看这里

 int margin = a.getDimensionPixelSize(
                com.android.internal.R.styleable.ViewGroup_MarginLayout_layout_margin, -1);
        if (margin >= 0) {
            leftMargin = margin;
            topMargin = margin;
            rightMargin = margin;
            bottomMargin = margin;
        } else {
            ...
        }

通用layout_margin获取到的值会优先赋予四个属性,否则才进行下一步,这也就解释了为什么layout_margin优先级最高

再往下走

       int horizontalMargin = a.getDimensionPixelSize(
                    R.styleable.ViewGroup_MarginLayout_layout_marginHorizontal, -1);
            int verticalMargin = a.getDimensionPixelSize(
                    R.styleable.ViewGroup_MarginLayout_layout_marginVertical, -1);

            if (horizontalMargin >= 0) {
                leftMargin = horizontalMargin;
                rightMargin = horizontalMargin;
            } else {
                leftMargin = a.getDimensionPixelSize(
                        R.styleable.ViewGroup_MarginLayout_layout_marginLeft,
                        UNDEFINED_MARGIN);
                if (leftMargin == UNDEFINED_MARGIN) {
                    mMarginFlags |= LEFT_MARGIN_UNDEFINED_MASK;
                    leftMargin = DEFAULT_MARGIN_RESOLVED;
                }
                rightMargin = a.getDimensionPixelSize(
                        R.styleable.ViewGroup_MarginLayout_layout_marginRight,
                        UNDEFINED_MARGIN);
                if (rightMargin == UNDEFINED_MARGIN) {
                    mMarginFlags |= RIGHT_MARGIN_UNDEFINED_MASK;
                    rightMargin = DEFAULT_MARGIN_RESOLVED;
                }
            }

通过这段代码我们可以得知layout_marginHorizontal > layout_marginLeft

再走下去就是startMargin的赋值

    startMargin = a.getDimensionPixelSize(
            R.styleable.ViewGroup_MarginLayout_layout_marginStart,
            DEFAULT_MARGIN_RELATIVE);
    endMargin = a.getDimensionPixelSize(
            R.styleable.ViewGroup_MarginLayout_layout_marginEnd,
            DEFAULT_MARGIN_RELATIVE);

可为什么layout_marginStart优先级别比layout_marginHorizontal高呢?分析doResolveMargins我们可以看到类似这样的代码

    // if left or right margins are not defined and if we have some start or end margin
       // defined then use those start and end margins.
       if ((mMarginFlags & LEFT_MARGIN_UNDEFINED_MASK) == LEFT_MARGIN_UNDEFINED_MASK
               && startMargin > DEFAULT_MARGIN_RELATIVE) {
           leftMargin = startMargin;
       }

可以得知后面又对leftMargin进行了赋值,这也是为什么ide会推荐我们声明layout_marginStart。
仔细看源码的话会发现layout_marginStart没那么简单,会受到安卓版本与LTR模式的影响,当然与本文主题关系不大,就不占篇幅了,感兴趣的朋友可自行了解