自定义AlertDialog对话框
- 概述
- 创建对话框片段
- 构建提醒对话框
- 添加列表
- 创建传统单选列表对话框
- 添加永久性多选列表和永久性单选列表
- 创建自定义布局
- 将事件传递回对话框的宿主
- 全屏显示对话框或将其显示为嵌入式片段
- 将 Activity 显示为大屏幕上的对话框
- 关闭对话框
概述
对话框是提示用户做出决定或输入额外信息的小窗口。对话框不会填充屏幕,通常用于需要用户采取行动才能继续执行的模式事件。
Dialog 类是对话框的基类。但通常应该避免直接对其实例化,而是使用下列子类之一:
- AlertDialog 此对话框可显示标题、最多三个按钮、可选择项列表或自定义布局。
- DatePickerDialog 或 TimePickerDialog 此对话框带有允许用户选择日期或时间的预定义界面。
注意:
Android 包含另一种名为
ProgressDialog 的对话框类,该类可显示带有进度条的对话框。不推荐使用此微件,因为它会在显示进度的情况下阻止用户与应用交互。如果需要指示加载进度或不确定的进度,您应遵循进度和 Activity 的设计指南,并在布局中使用
ProgressBar,而非 ProgressDialog。
以上的类可以定义对话框的样式与结构,但需要应用DialogFragment作为对话框的容器。DialogFragment 类提供创建对话框和管理其外观所需的所有控件,而非调用 Dialog 对象上的方法。
使用 DialogFragment 管理对话框可确保它能正确处理生命周期事件,如用户按“返回”按钮或旋转屏幕时。此外,DialogFragment 类还允许以嵌入式组件的形式在较大界面中重复使用对话框的界面,类似于传统的 Fragment(例如,当希望让对话框界面在大屏幕和小屏幕上具有不同外观时)。
注意:下文的对话框代码基于支持库的 android.support.v4.app.DialogFragment 类,而不是 android.app.DialogFragment。
创建对话框片段
通过扩展 DialogFragment 并在 onCreateDialog() 回调方法中创建 AlertDialog,可以完成各种对话框设计。
一个普通的对话框片段如下:
public class FireMissilesDialogFragment extends DialogFragment {
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
// Use the Builder class for convenient dialog construction
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
builder.setMessage(R.string.dialog_fire_missiles)
.setPositiveButton(R.string.fire, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
// FIRE ZE MISSILES!
}
})
.setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
// User cancelled the dialog
}
});
// Create the AlertDialog object and return it
return builder.create();
}
}
通过创建以上类的实例并调用对象上的show()方法,可以弹出对话框。
根据对话框的复杂度,可以在DialogFragment中实现各种其他回调方法,包括所有基本的片段生命周期方法。
构建提醒对话框
AlertDialog类允许您构建各种对话框设计,并且该类通常是需要的唯一对话框类。可以用AlertDialog.Builder类提供的API创建对话框。
添加列表
可通过AlertDialog API提供的三种列表:
- 传统的单选列表
- 永久性的单选列表(单选按钮)
- 永久性的多选列表(复选框)
创建传统单选列表对话框
如果要创建传统单选列表,可以使用 setItems() 方法:
@NonNull
@Override
public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
AlertDialog.Builder builder=new AlertDialog.Builder(getActivity());
builder.setTitle("标题")
.setItems(R.array.items_of_tranditional_single_select_dialog, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
String a=String.valueOf(which);
String selectContent=getResources().getStringArray(R.array.items_of_tranditional_single_select_dialog)[which];
Toast.makeText(getContext(),"你选择了"+selectContent,Toast.LENGTH_SHORT).show();
}
});
return builder.create();
}
由于列表出现在对话框的内容区域,因此对话框无法同时显示消息和列表,应通过 setTitle() 为对话框设置标题。如要为列表指定项目,可以调用 setItems(),并传递数组。或者可以使用 setAdapter() 指定列表。如此一来,便可使用ListAdapter ,以动态数据(如来自数据库的数据)支持列表。
如果选择通过ListAdapter 支持列表,务必使用 Loader ,以便内容以异步方式加载。 使用适配器构建布局 和 加载程序 指南中对此做了进一步描述。
注意:默认情况下,轻触列表项会关闭对话框,除非使用以下的某一种永久性选择列表
添加永久性多选列表和永久性单选列表
如要添加多选项(复选框)或单选项(单选按钮)列表,需要分别使用 setMultiChoiceItems() 或 setSingleChoiceItems() 方法。
@Override
public Dialog onCreateDialog(@Nullable Bundle savedInstanceState)
{
selectItems=new ArrayList<>();
AlertDialog.Builder builder=new AlertDialog.Builder(getActivity());
builder.setTitle("标题")
.setMultiChoiceItems(R.array.items_of_multi_select_dialog, checkedItems, new DialogInterface.OnMultiChoiceClickListener() {
@Override
public void onClick(DialogInterface dialog, int which, boolean isChecked) {
try {
if(isChecked)
{
selectItems.add(getResources().getStringArray(R.array.items_of_multi_select_dialog)[which]);
}
else
{
selectItems.remove(getResources().getStringArray(R.array.items_of_multi_select_dialog)[which]);
}
}
catch (Exception e)
{
e.printStackTrace();
}
}
})
.setPositiveButton("OK", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
}
});
return builder.create();
}
尽管传统列表和带有单选按钮的列表均提供“单选”操作,但若想保留用户的选择,则应使用 setSingleChoiceItems()。换言之,若希望在稍后再次打开对话框时显示用户的当前选择,则需创建带有单选按钮的列表。
创建自定义布局
如果想让对话框拥有自定义布局,需要在视图中自定义布局内容,然后通过调动AlertDialog.Builder 对象上的 setView() ,将该布局添加至 AlertDialog。
默认情况下,自定义布局会填充对话框窗口,但仍可使用AlertDialog.Builder 方法来添加按钮和标题。
一个自定义布局如下:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.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:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center_horizontal"
tools:context=".DialogTest.CustomLayoutDialogFragment">
<!-- TODO: Update blank fragment layout -->
<TextView
android:id="@+id/textview_customDialog"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:text="@string/hello_blank_fragment"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/button_customDialog" />
<Button
android:id="@+id/button_customDialog"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:text="Button"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/checkBox_customDialog" />
<ImageView
android:id="@+id/imageView_customDialog"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="@android:drawable/btn_star_big_on"
tools:src="@android:drawable/btn_star_big_on" />
<CheckBox
android:id="@+id/checkBox_customDialog"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:text="CheckBox"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/imageView_customDialog" />
</androidx.constraintlayout.widget.ConstraintLayout>
如果要扩展DialogFragment中的布局,需要通过 getLayoutInflater() 获取一个 LayoutInflater 并调用 inflate(),其中第一个参数是布局资源 ID,第二个参数是布局的父视图。然后,您可以调用 setView(),将布局放入对话框中。或者不需要使用LayoutInflater ,直接将layout资源ID传入setView()中也可以。
public class CustomLayoutDialogFragment extends DialogFragment {
@NonNull
@Override
public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
AlertDialog.Builder builder=new AlertDialog.Builder(getActivity());
//LayoutInflater inflater=requireActivity().getLayoutInflater();
builder.setView(R.layout.fragment_custom_layout_dialog)
.setPositiveButton("OK", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
}
})
.setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
}
});
builder.setOnKeyListener(new DialogInterface.OnKeyListener() {
@Override
public boolean onKey(DialogInterface dialog, int keyCode, KeyEvent event) {
return true;//屏蔽点击返回按钮取消对话框
}
});
AlertDialog alertDialog=builder.create();
alertDialog.setCanceledOnTouchOutside(false);//屏蔽对话框范围外的触屏取消
return alertDialog;
}
}
可以在自定义的对话框Fragment中获取视图组件对象并进行事件监听。
@Override
public void onResume() {
super.onResume();
Button btn=(Button)getDialog().findViewById(R.id.button_customDialog);
CheckBox checkBox=(CheckBox)getDialog().findViewById(R.id.checkBox_customDialog);
if (!btn.hasOnClickListeners())
{
btn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
}
});
}
if (!checkBox.hasOnClickListeners())
{
checkBox.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
}
});
checkBox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
}
});
}
}
将事件传递回对话框的宿主
如果需要将对话框的操作事件(如按钮点击,勾选等)或数据传递给弹出该对话框的宿主(Activity或Fragment)。需要定义接口。然后,从该对话框操作事件的宿主组件中实现这些接口。以上面自定义对话框为例。在CustomLayoutDialogFragment中定义接口,通过接口将事件传回宿主:
public class CustomLayoutDialogFragment extends DialogFragment {
public interface CustomLayoutDialogListener
{
public void onDialogPositiveClick(DialogFragment dialog);
public void onDialogNegativeClick(DialogFragment dialog);
}
CustomLayoutDialogListener customLayoutDialogListener;
@Override
public void onAttach(@NonNull Context context) {
super.onAttach(context);
try {
customLayoutDialogListener=(CustomLayoutDialogListener) context;
}
catch (ClassCastException e)
{ throw new ClassCastException(getActivity().toString()
+ " must implement NoticeDialogListener");
}
}
}
对话框的宿主 (AllKindsOfDialogActivity)会使用对话框片段的构造函数创建对话框实例,并通过 CustomLayoutDialogListener接口的实现接收接收对话框的事件:
public class AllKindsOfDialogActivity extends AppCompatActivity implements CustomLayoutDialogFragment.CustomLayoutDialogListener {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_all_kinds_of_dialog);
}
public void showCustomLayoutDialog(View view)
{
CustomLayoutDialogFragment customLayoutDialogFragment=new CustomLayoutDialogFragment();
customLayoutDialogFragment.show(getSupportFragmentManager(),"customDialog");
}
@Override
public void onDialogPositiveClick(DialogFragment dialog) {
Toast.makeText(this,"Pos",Toast.LENGTH_SHORT).show();
}
@Override
public void onDialogNegativeClick(DialogFragment dialog) {
Toast.makeText(this,"Neg",Toast.LENGTH_SHORT).show();
}
}
由于宿主 Activity 会实现 CustomLayoutDialogListener (由以上显示的 onAttach() 回调方法强制执行),因此对话框片段可使用接口回调方法向 Activity 传递点击事件:
@NonNull
@Override
public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
AlertDialog.Builder builder=new AlertDialog.Builder(getActivity());
//LayoutInflater inflater=requireActivity().getLayoutInflater();
builder.setView(R.layout.fragment_custom_layout_dialog)
.setPositiveButton("OK", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
customLayoutDialogListener.onDialogPositiveClick(CustomLayoutDialogFragment.this);
}
})
.setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
customLayoutDialogListener.onDialogNegativeClick(CustomLayoutDialogFragment.this);
}
});
builder.setOnKeyListener(new DialogInterface.OnKeyListener() {
@Override
public boolean onKey(DialogInterface dialog, int keyCode, KeyEvent event) {
return true;
}
});
AlertDialog alertDialog=builder.create();
alertDialog.setCanceledOnTouchOutside(false);
return alertDialog;
}
全屏显示对话框或将其显示为嵌入式片段
不能使用 AlertDialog.Builder 或其他 Dialog 对象来构建对话框。如果想让 DialogFragment 拥有嵌入能力,则必须在布局中定义对话框的界面,然后在 onCreateView() 回调中加载布局。
public class FullScreenDialogFragment extends DialogFragment {
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_full_screen_dialog, container, false);
}
/** The system calls this only when creating the layout in a dialog. */
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
// The only reason you might override this method when using onCreateView() is
// to modify any dialog characteristics. For example, the dialog includes a
// title by default, but your custom layout might not need it. So here you can
// remove the dialog title, but you must call the superclass to get the Dialog.
Dialog dialog = super.onCreateDialog(savedInstanceState);
dialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
return dialog;
}
}
以下代码可根据具体需要确定将片段显示为对话框或全屏界面:
public void showFullScreenDialog(View view)
{
FragmentManager fragmentManager = getSupportFragmentManager();
FullScreenDialogFragment fullScreenDialogFragment=new FullScreenDialogFragment();
//fullScreenDialogFragment.show(fragmentManager, "fullScreenDialogFragment");弹出片段
//以下为全屏显示
//FragmentTransaction transaction = fragmentManager.beginTransaction();
// For a little polish, specify a transition animation
transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
// To make it fullscreen, use the 'content' root view as the container
// for the fragment, which is always the root view for the activity
// transaction.add(R.id.containerOfAllDialog, fullScreenDialogFragment)
.addToBackStack(null).commit();//需要用id指定宿主的视图作为全屏显示片段的容器
}
全屏显示中用到了片段事务,具体可以参考 执行片段事务
将 Activity 显示为大屏幕上的对话框
相对于在小屏幕上将对话框显示为全屏界面,也可以在大屏幕上将 Activity 显示为对话框,从而达到相同的效果。
如要仅在大屏幕上将 Activity 显示为对话框,请将 Theme.Holo.DialogWhenLarge 主题应用于 清单文件元素:
<activity android:theme="@android:style/Theme.Holo.DialogWhenLarge" >
关闭对话框
当用户轻触使用 AlertDialog.Builder 创建的任何操作按钮时,系统会关闭对话框。
系统还会在用户轻触某个对话框列表项时关闭对话框,除非该列表使用单选按钮或复选框。否则,可以通过在 DialogFragment 上调用 dismiss() 来手动关闭对话框。
如需在对话框消失时执行特定操作,可以在 DialogFragment 中实现 onDismiss() 方法。
还可取消对话框。此特殊事件表示用户显式离开对话框,且并未完成任务。如果用户按“返回”按钮、轻触对话框区域外的屏幕,或者在 Dialog 上显式调用 cancel()(例如,为响应对话框中的“取消”按钮),就会发生这种情况。
如上例所示,可以通过在 DialogFragment 类中实现 onCancel() 来响应取消事件。