由Dialog设置背景引发的思考

一般写Dialog的时候,都会设置宽度为屏幕的宽度

1
2
3
Window window = getDialog().getWindow(); WindowManager.LayoutParams params = window.getAttributes();
params.gravity = Gravity.BOTTOM;
params.width = WindowManager.LayoutParams.MATCH_PARENT; window.setAttributes(params);

但是有时候会出现一些奇怪的问题

layout

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
<?xml version="1.0" encoding="utf-8"?> <LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"> <LinearLayout android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView android:id="@+id/menu1"
android:layout_width="match_parent"
android:layout_height="50dp"
android:gravity="center"
android:padding="15dp"
android:text="重新登录"
android:textColor="#fa0000"
android:background="@drawable/seletcor_top_bg"
/>
<View android:layout_width="match_parent"
android:layout_height="1dp"
android:background="#f5f4f4"/>
<TextView android:id="@+id/menu2"
android:layout_width="match_parent"
android:layout_height="50dp"
android:gravity="center"
android:padding="15dp"
android:text="退出"
android:textColor="#2763b6"
android:background="@drawable/seletcor_bottom_bg"
/>
</LinearLayout> <Button android:id="@+id/btn_cancel"
android:layout_width="match_parent"
android:layout_height="50dp"
android:layout_marginTop="15dp"
android:background="@drawable/seletcor_round_bg"
android:text="取消"
/> </LinearLayout>

效果展示

按照上面的代码写的时候,效果是这样的。

20180530152766370374459.png

这不是我们想要的效果。

当我们多设置一步 A
window.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
它会去除所有的padding
效果是这样的。

20180530152766382094867.png

这个也不是我们想要的效果

当我们多设置一步 B
window.getDecorView().setBackground(new ColorDrawable(Color.TRANSPARENT));
效果是这样的。

20180530152766397087558.png

看起来符合我们的要求,但是这个边距实际上是系统提供的,我们不能自己设置。

代码分析

  • window.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));为什么会去除padding?
1
public abstract void setBackgroundDrawable(Drawable drawable);

这个方法是个抽象方法,它的唯一实现是PhoneWindow

1
2
3
4
5
6
7
8
9
10
11
12
13
@Override
public final void setBackgroundDrawable(Drawable drawable) {
if (drawable != mBackgroundDrawable || mBackgroundResource != 0) {
mBackgroundResource = 0;
mBackgroundDrawable = drawable;
if (mDecor != null) {
mDecor.setWindowBackground(drawable);
}
if (mBackgroundFallbackResource != 0) {
mDecor.setBackgroundFallback(drawable != null ? 0 : mBackgroundFallbackResource);
}
}
}

这里主要看mDecor.setWindowBackground(drawable);

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public void setWindowBackground(Drawable drawable) {
if (getBackground() != drawable) {
setBackgroundDrawable(drawable);
if (drawable != null) {
mResizingBackgroundDrawable = enforceNonTranslucentBackground(drawable,
mWindow.isTranslucent() || mWindow.isShowingWallpaper());
} else {
mResizingBackgroundDrawable = getResizingBackgroundDrawable(
getContext(), 0, mWindow.mBackgroundFallbackResource,
mWindow.isTranslucent() || mWindow.isShowingWallpaper());
}
if (mResizingBackgroundDrawable != null) {
mResizingBackgroundDrawable.getPadding(mBackgroundPadding);
} else {
mBackgroundPadding.setEmpty();
}
drawableChanged();
}
}

setBackgroundDrawable(drawable);方法会进入ViewsetBackgroundDrawable方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
if (background != null) {
Rect padding = sThreadLocal.get();
if (padding == null) {
padding = new Rect();
sThreadLocal.set(padding);
}
resetResolvedDrawablesInternal();
background.setLayoutDirection(getLayoutDirection());
if (background.getPadding(padding)) {
resetResolvedPaddingInternal();
switch (background.getLayoutDirection()) {
case LAYOUT_DIRECTION_RTL:
mUserPaddingLeftInitial = padding.right;
mUserPaddingRightInitial = padding.left;
internalSetPadding(padding.right, padding.top, padding.left, padding.bottom);
break; case LAYOUT_DIRECTION_LTR:
default:
mUserPaddingLeftInitial = padding.left;
mUserPaddingRightInitial = padding.right;
internalSetPadding(padding.left, padding.top, padding.right, padding.bottom);
}
mLeftPaddingDefined = false;
mRightPaddingDefined = false;
}
}

其中background.getPadding(padding)这句话很有意思。

1
2
3
4
5
6
/**
* Return in padding the insets suggested by this Drawable for placing * content inside the drawable's bounds. Positive values move toward the * center of the Drawable (set Rect.inset). * * @return true if this drawable actually has a padding, else false. When false is returned,
* the padding is always set to 0. */
public boolean getPadding(@NonNull Rect padding) {
padding.set(0, 0, 0, 0);
return false; }

当它的参数有值的时候,返回的是true,当它返回false的时候,padding都会被设置为0。

因为我们传入的是ColorDrawable,它没有重写这个方法,所以它返回的永远是false。方法里面不执行。
回到DecorViewsetWindowBackground方法。

1
2
3
4
5
if (mResizingBackgroundDrawable != null) {
这句话会重置mBackgroundPadding为0 0 0 0
mResizingBackgroundDrawable.getPadding(mBackgroundPadding); } else {
mBackgroundPadding.setEmpty(); }
drawableChanged();

drawableChanged会重新设置padding,所以window.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));会将默认的padding清0。

1
2
3
4
5
6
7
8
9
10
11
12
private void drawableChanged() {
if (mChanging) {
return;
}
setPadding(mFramePadding.left + mBackgroundPadding.left,
mFramePadding.top + mBackgroundPadding.top,
mFramePadding.right + mBackgroundPadding.right,
mFramePadding.bottom + mBackgroundPadding.bottom);
requestLayout();
invalidate();
}
  • 为什么window.getDecorView().setBackground(new ColorDrawable(Color.TRANSPARENT));会保留padding?

也很好理解了。因为它实际上直接调用了ViewsetBackgroundDrawable方法。根据之前的逻辑,到了if (background.getPadding(padding))这一步它是不会重新设置padding的。

总结

当同时设置Viewpadding背景的时候。调用顺序很重要。