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)