layout_marginEnd 导致动态设置setLayoutParams失效的巨坑

targetSdkVersion >= JELLY_BEAN_MR1(17)时,在xml布局中我们设定marginLeft或marginRight会看到这种提示

“Consider adding android:layout_marginEnd="@dimen/xx" to better support right-to-left layouts less... ”

意思是说让我们加上layout_marginEnd来更好的支持布局方向的改变,一般情况下我们会加上。不过加上后会导致动态设置setLayoutParams失效,而且只是marginLeft、marginRight,加上后在targetSdkVersion<17 时也没问题,但在targetSdkVersion>=17时就坑了。

MarginLayoutParams是由父布局的generateLayoutParams产生的,以FrameLayout作为父布局为例。

@Override
    public LayoutParams generateLayoutParams(AttributeSet attrs) {
        return new FrameLayout.LayoutParams(getContext(), attrs);
    }

LayoutParams 继承自 MarginLayoutParams,在创建MarginLayoutParams时有以下几个特殊的地方。

//首先解析startMargin和endMargin
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_marginEnd或layout_marginStart,如果设置了会做一个标志         
if (isMarginRelative()) {
                   mMarginFlags |= NEED_RESOLUTION_MASK;
                }
//当targetSdkVersion<17或不支持right-to-left时走兼容模式
final boolean hasRtlSupport = c.getApplicationInfo().hasRtlSupport();
            final int targetSdkVersion = c.getApplicationInfo().targetSdkVersion;
            if (targetSdkVersion < JELLY_BEAN_MR1 || !hasRtlSupport) {
                mMarginFlags |= RTL_COMPATIBILITY_MODE_MASK;
            }

创建时根据设置和targetSdkVersion设置了两处flag,这有什么用呢

/**
         * This will be called by {@link android.view.View#requestLayout()}. Left and Right margins
         * may be overridden depending on layout direction.
         */
        @Override
        public void resolveLayoutDirection(int layoutDirection) {
            setLayoutDirection(layoutDirection);

            // No relative margin or pre JB-MR1 case or no need to resolve, just dont do anything
            // Will use the left and right margins if no relative margin is defined.
            if (!isMarginRelative() ||
                    (mMarginFlags & NEED_RESOLUTION_MASK) != NEED_RESOLUTION_MASK) return;

            // Proceed with resolution
            doResolveMargins();
        }

和注释说的一样,view的requestLayout后会调用LayoutParams的resolveLayoutDirection方法(view的measure里最终会调到),方法里先做了一个判断:如果没有设置layout_marginEnd或layout_marginStart,或者没有重新设置margin的标志位则直接返回,因为我们设置了layout_marginEnd,所以会调用doResolveMargins方法。

private void doResolveMargins() {
            //如果是兼容模式:targetSdkVersion<17或不支持right-to-left时走兼容模式
            if ((mMarginFlags & RTL_COMPATIBILITY_MODE_MASK) == RTL_COMPATIBILITY_MODE_MASK) {
                // 如果marginLeft和marginRight没设置,则用marginStart和marginEnd给他们赋值
                // 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 {
            //如果不是兼容模式,无论是设置marginStart、marginEnd或都设置了,都要覆盖已经设置的marginLeft和marginRight。marginStart或marginEnd没设置的话赋值为零。
                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;
        }

上边的注释已经很清楚了,在targetSdkVersion<17下,走的是兼容模式,所以我们的marginLeft和marginRight可以生效,而在targetSdkVersion>=17,marginLeft和marginRight会被marginStart、marginEnd覆盖。

public void change(){
        FrameLayout.LayoutParams params = (FrameLayout.LayoutParams) button.getLayoutParams();
        int dy = 40;
        params.setMargins(params.leftMargin+dy, params.topMargin, params.rightMargin, params.bottomMargin+dy);
        button.setLayoutParams(params);
    }

因此上面动态设置布局参数的方法,在targetSdkVersion>=17并且设置了marginStart和marginEnd时,你会发现marginLeft并没有改变,但是marginTop可以正常改变。

解决方法,在在targetSdkVersion>=17并且设置了marginStart和marginEnd时,用

public void setMarginEnd(int end)
public void setMarginStart(int start)

而不要使用

public void setMargins(int left, int top, int right, int bottom)

你可能感兴趣的:(android)