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)