DialogFragment 使用总结

DialogFragment意义

       在Android里实现一个对话框一般有这几种方式:Dialog(继承重写Dialog实现一个自定义的Dialog)、AlertDialog(Android原生提供的对话框,底层是继承Dialog实现)、PopupWindow(用弹出悬浮框,实现对话框)。

       它们与activity的生命周期不是捆绑的,后面google推荐使用DialogFragment来取代它们。DialogFragment本质是Fragment,有Fragment的生命周期并且与创建它的activity有捆绑,便于Activity更好的控制管理DialogFragment。

       随屏幕旋转(横竖屏幕切换)DialogFragment对话框随之自动调整对话框大小。AlertDialog和PopupWindow随屏幕切换而消失,并且如果处理不当很可能引发异常。

DialogFragment的创建

以Dialog创建DialogFragment

       类似如下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Override
public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
//创建对话框,需要返回dialog
AlertDialog.Builder builder = new AlertDialog.Builder(getContext());
builder.setTitle("MyDialog1")
.setMessage("以Dialog创建DialogFragment")
.setPositiveButton("确定", null)
.setNegativeButton("取消", null)
.setCancelable(false);

return builder.create();
}

@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
//此方法在视图已经创建后返回的,但是这个view还没有添加到父级中,我们在这里可以重新设定view的各个数据
}

       在activity里显示对话框:

1
2
MyDialog1 myDialog1 = new MyDialog1();
myDialog1.show(getSupportFragmentManager(), "MyDialog1");

       当然也可以使用自定义的布局来创建:

1
2
3
4
5
6
7
8
9
10
11
@Override
public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
//创建对话框,需要返回dialog
AlertDialog.Builder builder = new AlertDialog.Builder(getContext());
// 设置主题的构造方法
// AlertDialog.Builder builder = new AlertDialog.Builder(getActivity(), R.style.CustomDialog);
LayoutInflater inflater = getActivity().getLayoutInflater();
View view = inflater.inflate(R.layout.dialog_sure, null);
builder.setView(view);
return builder.create();
}

       还有直接使用Dialog的方法:

1
2
3
4
5
6
7
8
9
10
@Override
public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
LayoutInflater inflater = getActivity().getLayoutInflater();
View view = inflater.inflate(R.layout.dialog_sure, null);
Dialog dialog = new Dialog(getActivity());
// 设置主题的构造方法
// Dialog dialog = new Dialog(getActivity(), R.style.CustomDialog);
dialog.setContentView(view);
return dialog;
}

以布局View创建DialogFragment

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
36
37
38
39
public class MyDialog2 extends DialogFragment {

@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.dialog_input, container, false);
return view;
}

@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
/*
此方法在视图View已经创建后返回的,但是这个view 还没有添加到父级中。
我们在这里可以重新设定view的各个数据,但是不能修改对话框最外层的ViewGroup的布局参数。
因为这里的view还没添加到父级中,我们需要在下面onStart生命周期里修改对话框尺寸参数
*/
}

/**
* 设置主题需要在 onCreate() 方法中调用 setStyle() 方法
*/
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setStyle(DialogFragment.STYLE_NO_TITLE, R.style.CustomDialog);
}

@Override
public void onStart() {
/*
因为View在添加后,对话框最外层的ViewGroup并不知道我们导入的View所需要的的宽度。 所以我们需要在onStart生命周期里修改对话框尺寸参数
*/
WindowManager.LayoutParams params = getDialog().getWindow().getAttributes();
params.width = ViewGroup.LayoutParams.MATCH_PARENT;
getDialog().getWindow().setAttributes(params);
super.onStart();
}
}

DialogFragment的设置

宽高设置

       DialogFragment在onCreate()和onCreateView()中设置布局大小无效,因为onCreate()和onCreateView()生命周期在onStart()生命周期之前,此时还未调用Dialog.show()方法,设置大小无效。所以要在onStart方法中设置大小:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/**
* 修改布局的大小
*/
@Override
public void onStart() {
super.onStart();
resizeDialogFragment();
}

private void resizeDialogFragment() {
Dialog dialog = getDialog();
if (null != dialog) {
Window window = dialog.getWindow();
WindowManager.LayoutParams lp = getDialog().getWindow().getAttributes();
lp.height = (25 * ScreenUtil.getScreenHeight(getContext()) / 32);//获取屏幕的宽度,定义自己的宽度
lp.width = (8 * ScreenUtil.getScreenWidth(getContext()) / 9);
if (window != null) {
window.setLayout(lp.width, lp.height);
}
}
}

改变对话框的显示位置

       改变对话框的显示位置,将对话框放到布局下面,也就是屏幕下方:

1
2
3
4
5
6
7
8
@Override
public void onStart() {
WindowManager.LayoutParams params = getDialog().getWindow().getAttributes();
params.width = ViewGroup.LayoutParams.MATCH_PARENT;
params.gravity = Gravity.BOTTOM; //将对话框放到布局下面,也就是屏幕下方
getDialog().getWindow().setAttributes((WindowManager.LayoutParams) params);
super.onStart();
}

将对话框的宽或者高铺满屏幕

       将对话框的宽或者高铺满屏幕,第一种方法需要在styles.xml文件里,添加一个没有内边距的style,如下:

1
2
3
4
5
6
7
8
<style name="dialogFullScreen1" parent="Theme.AppCompat.Dialog">
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item>
<item name="android:padding">0dp</item>
<item name="android:windowBackground">@android:color/white</item>
<item name="android:textColor">@android:color/black</item>
</style>

       第二种方法设置android:windowFullscreen属性:

1
2
3
4
5
6
7
8
<style name="dialogFullScreen2" parent="Theme.AppCompat.Dialog">
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item>
<item name="android:windowBackground">@android:color/white</item>
<item name="android:textColor">@android:color/black</item>
<item name="android:windowFullscreen">true</item>
</style>

       如果在代码中设置高度也MATCH_PARENT,第一种会保留手机上的导航栏,而第二种会连导航栏都覆盖掉。

       也可以在创建DialogFragment对话框的时候添加style,如下:

1
2
3
MyDialog2 myDialog2 = new MyDialog2();
myDialog2.setStyle(DialogFragment.STYLE_NORMAL, R.style.dialogFullScreen2);//添加style
myDialog2.show(getSupportFragmentManager(), "MyDialog");

设置点击外部空白处不会关闭对话框

       可以在styles中设置:

1
2
3
4
5
6
<!--    设置点击外部空白处不会关闭对话框-->
<style name="dialogNotDismiss" parent="Theme.AppCompat.Dialog">
<item name="android:windowBackground">@android:color/white</item>
<item name="android:textColor">@android:color/black</item>
<item name="android:windowCloseOnTouchOutside">false</item>
</style>

       也可以在代码中设置:

1
2
3
4
5
6
7
@Override
public void onStart() {
...
//getDialog().setCanceledOnTouchOutside(false);
getDialog().setCancelable(false);//这个会连返回键也屏蔽掉
super.onStart();
}

设置在弹出对话框后同时弹出软键盘

       代码如下:

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
public class MyDialog4 extends DialogFragment {

@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.dialog_input, container, false);
return view;
}

@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
EditText editPassword = view.findViewById(R.id.et_input);
editPassword.requestFocus();//设置焦点
getDialog().getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE);//设置输入键盘可见
super.onViewCreated(view, savedInstanceState);
}

@Override
public void onStart() {
WindowManager.LayoutParams params = getDialog().getWindow().getAttributes();
params.width = ViewGroup.LayoutParams.MATCH_PARENT;
// params.height = ViewGroup.LayoutParams.MATCH_PARENT;
params.gravity = Gravity.BOTTOM; //将对话框放到布局下面,也就是屏幕下方
getDialog().getWindow().setAttributes((WindowManager.LayoutParams) params);
super.onStart();
}
}

       效果如下:

在Fragment里启动对话框

       与activity里一样,唯一的区别是如果打算依然用老套的onActivityResult来向下传值,那么就需要设置一个目标Fragment,在下面的代码里setTargetFragment()方法就是起到这个作用的,在下面的代码里我们用MyDialog5启动了MyDialog2:

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
public class MyDialog5 extends DialogFragment {

@NonNull
@Override
public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
//创建对话框,需要返回dialog
AlertDialog.Builder builder = new AlertDialog.Builder(getContext());
builder.setTitle("测试Dialog");
builder.setMessage("启动另外一个对话框");
builder.setPositiveButton("启动", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
MyDialog2 myDialog2 = new MyDialog2();
myDialog2.setTargetFragment(MyDialog5.this, 300);
myDialog2.setStyle(DialogFragment.STYLE_NORMAL, R.style.dialogFullScreen1);
myDialog2.show(getFragmentManager(), "myDialog2");

}
});
return builder.create();
}

@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
//此方法在视图已经创建后返回的,但是这个view还没有添加到父级中,我们在这里可以重新设定view的各个数据
}

@Override
public void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
super.onActivityResult(requestCode, resultCode, data);
//这里可以返回 MyDialog2 Fragment的数据
}
}

设置圆角

       设置圆角失败,实际上是设置的背景图片被Dialog自带的背景遮盖了,导致圆角无法显示。所以设置一下透明背景就可以了。注意设置DecorView的背景与设置Window的背景是有区别的,设置Window的背景 与设置DecorView背景相比,该方法会将dialog自带的padding内边距去掉。

1
2
3
4
5
6
7
8
9
10
11
@Override
public void onStart() {
WindowManager.LayoutParams params = getDialog().getWindow().getAttributes();
params.width = ViewGroup.LayoutParams.MATCH_PARENT;
getDialog().getWindow().setAttributes((WindowManager.LayoutParams) params);
//方法一 设置DecorView背景
//getDialog().getWindow().getDecorView().setBackground(new ColorDrawable(Color.TRANSPARENT));
//方法二 或者设置Window背景
getDialog().getWindow().setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
super.onStart();
}

       效果如下:

       设置DecorView背景:

DialogFragment设置DecorView背景

       设置Window背景:

DialogFragment设置Window背景

       代码详见DialogTest

参考资料:
观心静 Android开发 DialogFragment对话框详解
他叫自己MR.张 Android 必知必会 - DialogFragment 使用总结
Yif DialogFragment 使用

Fork me on GitHub