对话框是一个小窗口,提示用户做出决定或输入附加信息。对话框不会填满屏幕,通常用于一些在用户执行某些操作前的提前选择。
日期和时间对话框
Dialog 类是对话框的基类,但是我们不应该直接去实例化一个 Dialog 类,而是要实例化它的子类:
- AlertDialog:可以显示标题、最多三个按钮、可选项目列表或自定义布局的对话框。
- DatePickerDialog:带有预定义UI的对话框,允许用户选择日期。
- TimePickerDialog:带有预定义UI的对话框,允许用户选择时间。
上面这些类定义对话框的结构和样式,但是你应该使用 DialogFragment 作为对话框容器而不是使用上面的 Dialog 子类,DialogFragment 类提供了创建对话框并管理其外观所需的所有控件。使用 DialogFragment 管理对话框可以确保它正确地处理生命周期事件,例如当用户按下后退按钮或旋转屏幕时。DialogFragment 类还允许您将对话框的 UI 作为可嵌入组件重用到更大的 UI 中,就像传统的 Fragment 一样(例如,当您希望对话框 UI 在大屏幕和小屏幕上以不同的方式显示时)。
创建一个 DialogFragment
通过扩展 DialogFragment 并在 onCreateDialog() 回调方法中创建 AlertDialog,您可以完成各种各样的对话框设计,包括自定义布局和对话框设计指南中描述的那些。
例如,这里有一个基本的 AlertDialog,它是在 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
| public class MyDialogFragment extends DialogFragment {
@NonNull
@Override
public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
// 创建 AlertDialog
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
//添加对话框提示信息
builder.setMessage("一个测试弹框")
//设置两个按钮文本和监听
.setPositiveButton("确认", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
ToastUtils.showToast(getContext(), "点击了确认");
}
}).setNegativeButton("取消", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
ToastUtils.showToast(getContext(), "点击了取消");
}
});
return builder.create();
}
}
|
然后在 Activity 中调用显示这个弹框:
1
2
3
| public void testSimpleDialog(View view) {
new MyDialogFragment().show(getSupportFragmentManager(), "TestSimpleDialog");
}
|
显示弹框结果
上面演示了如何去显示一个 FragmentDialog,你可以通过从 Activity 中调用 getSupportFragmentManager() 或从 Fragment 中调用 getFragmentManager() 来获得 FragmentManager。第二个参数 “TestSimpleDialog” 是一个惟一的标记名称,系统使用它在必要时保存和恢复片段状态。标记还允许您通过调用 findFragmentByTag() 获得片段的句柄。
事实上我们可以不使用 FragmentDialog 来作为对话框容器,直接使用 AlertDialog 显示,但是不推荐你这样做,至于什么原因请往下看。
AlertDialog 显示
我们完全可以不使用 FragmentDialog,这是因为最早创建一个对话框的方式如下,当时还没有 FragmentDialog 这个类。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
| public void testSimpleAlertDialog(View view) {
createAlertDialog().show();
}
private AlertDialog createAlertDialog(){
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setMessage("一个测试弹框")
.setPositiveButton("确认", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
ToastUtils.showToast(MainActivity.this, "点击了确认");
}
}).setNegativeButton("取消", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
ToastUtils.showToast(MainActivity.this, "点击了取消");
}
});
return builder.create();
}
|
上面代码通过 AlertDialog.show()
方法来显示了弹框,显示的样式上和上图一致,没有什么变化。
一个 AlertDialog 可以设置标题、显示内容、动作按钮,如下图:
一个 AlertDialog 的三个元素
其实通过上面的两个弹框的代码你可以看出来了,创建 AlertDialog 要使用它的 Builder 类来实现,可以给设置标题、显示内容、动作按钮。本文出自水寒的博客,转载请说明出处:https://dp2px.com
第一步:创建 AlertDialog.Builder 对象
1
| AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
|
第二步:设置标题和内容
1
| builder.setMessage(R.string.dialog_message).setTitle(R.string.dialog_title);
|
第三步:调用 Builder 的 create() 方法创建 AlertDialog 对象。
1
| AlertDialog dialog = builder.create();
|
第四步:添加动作按钮(可选)
1
2
3
4
5
6
7
8
9
10
| builder.setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
// User clicked OK button
}
});
builder.setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
// User cancelled the dialog
}
});
|
Positive 按钮你应该使用它来接受并继续操作(“OK”操作)。
Negative 按钮你应该使用这个来取消操作。
Neutral 按钮当用户可能不想继续执行操作,但又不一定想要取消时,您应该使用此功能。
显示内容上除了上面的 setMessage() 去设置一行提示文本外,我们还可以通过 setItems() 方法来显示一个列表对话框。
1
2
3
4
5
6
7
8
9
10
11
| @Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
builder.setTitle(R.string.pick_color)
.setItems(R.array.colors_array, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
//...
}
});
return builder.create();
}
|
显示一个列表对话框
这些样式可能并不是我们想要的,有时候我们需要自定义内容样式,我们可以使用 setView() 方法来实现这种自定义。
1
2
3
4
| AlertDialog.Builder builder = new AlertDialog.Builder(this);
View dialogView = getLayoutInflater().inflate(R.layout.custome_dialog_view, null);
builder.setView(dialogView);
builder.create().show();
|
使用 setView() 设置一个 View 对象给弹框即可,布局内容如下(使用的约束布局):
自定义弹框布局的约束关系
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
| <?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:padding="40dip"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="@+id/imageView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginEnd="8dp"
app:layout_constraintBottom_toTopOf="@+id/textView"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_chainStyle="packed"
app:srcCompat="@mipmap/ic_launcher" />
<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="40dp"
android:layout_marginEnd="8dp"
android:text="水寒的博客:DP2PX.COM"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/imageView" />
</android.support.constraint.ConstraintLayout>
|
自定义的弹框
DialogFragment 的使用
从上面的这些弹框演示中可以看出来,DialogFragment 完全可以不存在,因为创建和显示一个弹框的内容和 DialogFragment 无关, DialogFragment 只是对弹框的声明周期和做一些显示和关闭的控制逻辑。所以严格意义上来说 DialogFragment 并不是弹框,而是一个弹框的控制和生命周期的包裹。
DialogFragment 需要确保 Fragment 和 Dialog 状态发生的情况保持一致。 为此,它会监视对话框中的关闭事件,并在事件发生时删除其自身的状态。 这意味着您应该使用 show(android.app.FragmentManager, java.lang.String) 或 show(android.app.FragmentTransaction, java.lang.String) 向您的 UI 添加 DialogFragment 实例,因为它们可以跟踪如何 退出对话框时, DialogFragment 应该删除自身。还有一个重要原因是直接使用 AlertDialog 容易引起内存泄漏,举两个例子:
- 如果 dialog 消失时做了 1s 的动画,就可能出现 activity 被 finish 了,但 dialog 还存在的情况,出现内存泄漏。
- Message 是任何线程共用的,looper 会不停的从阻塞队列 messageQueue 中取出 message 进行处理。当没有可消费的 message 对象时,就会开始阻塞,如果最后取出的正好是 mDismissMessage,那么也会出现泄漏。
上面的弹框 AlertDialog 代码都可以放在 DialogFragment 的 onCreateDialog() 方法中去创建,其实我们也可以直接在 DialogFragment 中创建一个 View 返回显示。
1
2
3
4
5
6
7
8
9
10
| public class MyDialogFragment2 extends DialogFragment {
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater,
@Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View view = requireActivity().getLayoutInflater().inflate(R.layout.custome_dialog_view, null);
return view;
}
}
|
DialogFragment 数据传递
我们的 Activity 和 DialogFragment 的数据传递和 Fragment 之间的数据传递是类似的,一个是我们需要将 Activity 中的数据变化通知到 DialogFragment,另一个是 DialogFragment 中的数据变化反馈到 Activity. 下面通过一个案例来说明一下:
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
40
41
42
43
44
45
46
47
48
49
50
51
52
| public class MyDialogFragment3 extends DialogFragment {
private TextView mTextView;
private EditText mEditTextView;
private OnMyDialogListener mDialogListener;
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater,
@Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View view = requireActivity().getLayoutInflater().inflate(R.layout.custome_dialog_edit_view, null);
mEditTextView = view.findViewById(R.id.editText);
mTextView = view.findViewById(R.id.textView2);
mEditTextView.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
@Override
public void afterTextChanged(Editable s) {
if(mDialogListener != null){
mDialogListener.editChanged(getEditTextContent());
}
}
});
return view;
}
public interface OnMyDialogListener{
public void editChanged(String content);
}
public void setMyDialogListener(OnMyDialogListener listener){
mDialogListener = listener;
}
public String getEditTextContent(){
if(mEditTextView == null) return "";
return mEditTextView.getText().toString();
}
public void changeTextShow(String text){
if(mTextView == null) return;
mTextView.setText(text);
}
}
|
其实过程很简单,很容易实现,外部的 Activity 中的 DialogFragment 对象可以监听我们定义的事件变化接口。
1
2
3
4
5
6
7
8
| MyDialogFragment3 dialogFragment = new MyDialogFragment3();
dialogFragment.setMyDialogListener(new MyDialogFragment3.OnMyDialogListener() {
@Override
public void editChanged(String content) {
ToastUtils.showToast(MainActivity.this, content);
}
});
dialogFragment.show(getSupportFragmentManager(), "TestDialogFragmentDataRecive");
|
可以在示例代码中看到效果,本文所有代码均在 GitHub 仓库:https://github.com/lxqxsyu/InnerShareCode11
根据屏幕宽度选择是否将弹框嵌入到界面
在 UI 设计中,您可能希望 UI 的一部分在某些情况下显示为对话框,但在其他情况下显示为全屏或嵌入式片段(可能取决于设备是大屏幕还是小屏幕)。DialogFragment 类为您提供了这种灵活性,因为它仍然可以作为一个可嵌入的片段。
下面是一个 DialogFragment 示例,它既可以作为对话框出现,也可以作为可嵌入的片段出现。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| boolean isLargeLayout = false;
public void testDialogCanEmbeddedFragment(View view) {
FragmentManager fragmentManager = getSupportFragmentManager();
MyDialogFragment4 dialogFragment = new MyDialogFragment4();
FragmentTransaction transaction = fragmentManager.beginTransaction();
if (isLargeLayout) {
dialogFragment.show(fragmentManager, "TestDialogCanEmbeddedFragment");
} else {
transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
transaction.add(R.id.fragment_container, dialogFragment)
.addToBackStack(null).commit();
}
isLargeLayout = !isLargeLayout;
}
|
可以点击两次查看不同的显示效果。
关闭弹框
当用户触摸 AlertDialog 创建的任何操作按钮时。生成器,系统将为您取消对话框。当用户触摸对话框列表中的项目时,系统还会关闭对话框,除非该列表使用单选按钮或复选框。否则,您可以通过调用 DialogFragment 上的 remove() 来手动取消对话框。如果需要在对话框消失时执行某些操作,可以在对 DialogFragment 中实现 onDismiss() 方法。
DialogFragment 提供了两个关闭的方法,分别是 dismiss() 和 dismissAllowingStateLoss(),前者对应的是 fragmentTransaction.commit(),后者对应的是 fragmentTransaction.commitAllowingStateLoss()。用 dismissAllowingStateLoss() 的好处是可以让我们忽略异步关闭 dialog 时的状态问题,让我们不用考虑当前 activity 的状态,这会减少很多线上的崩溃。
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 void dismiss() {
dismissInternal(false);
}
public void dismissAllowingStateLoss() {
dismissInternal(true);
}
void dismissInternal(boolean allowStateLoss) {
mDismissed = true;
mShownByMe = false;
if (mDialog != null) {
mDialog.dismiss();
mDialog = null;
}
mViewDestroyed = true;
// 处理多个dialogFragment的问题
if (mBackStackId >= 0) {
getFragmentManager().popBackStack(mBackStackId,
FragmentManager.POP_BACK_STACK_INCLUSIVE);
mBackStackId = -1;
} else {
FragmentTransaction ft = getFragmentManager().beginTransaction();
ft.remove(this); // 移除当前的fragment
if (allowStateLoss) {
ft.commitAllowingStateLoss();
} else {
ft.commit();
}
}
}
|
另外说一下 dismiss() 和 cancel() 的区别:
- dismiss() 表示用户离开了对话框,不完成任何任务,等于忽略了对话框。
- cancel 表示用户主动取消了当前操作,是一个主动的选择。
- 调用 onCancel() 后默认会立即调用 onDismiss().
- 调用 dialogFragment.dismiss() 后并不会触发 onCancel().
- 当用户在对话框中按 “ok” 按钮后,从视图中移除对话框时,会自动调用 onDismiss().
本文源码下载地址:https://github.com/lxqxsyu/InnerShareCode11