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)
發佈了47 篇原創文章 · 獲贊 3 · 訪問量 2萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章