对话框
对话框是一种提示用户去做出选择或输入其他信息的小窗口。 对话框不填充屏幕并且通常被用于在执行前需要用户做出决定的模态事件。
对话框设计
阅读 Dialogs 设计指南,获取包括语言规范等关于如何设计对话框的更多信息。
- 这种对话框可以显示一个标题,最多三个按钮,一个可选项列表或用户自定义布局。
- 这种使用预定义界面的对话框可以允许用户选择日期或时间。
AlertDialog
DatePickerDialog
or TimePickerDialog
避免使用 ProgressDialog
Android 拥有另外一种叫做 ProgressDialog
的对话框类,它能显示一个带进度条的对话框的对话框。然而如果你需要表明正在加载或有不确定的进度,你应该遵循 Progress & Activity 的设计指南并在你的布局中使用 ProgressBar
。
虽然这些类已经定义了对话框的样式和结构,但是你应该使用 DialogFragment 作为对话框的容器。你不用调用 Dialog 对象的方法,因为 DialogFragment 类能提供所有的控制手段,你只需创建你的对话框并控制它的外观。
使用 DialogFragment 来管理对话框,可以确保对话框能正确的处理生命周期内的事件(例如:当用户按下回退键或旋转屏幕)。就像传统的 Fragment 一样,在一个大的用户界面中,DialogFragment
同样允许你把对话框界面当做嵌入式组件来重用(比如你想在大小不同的界面里显示不同的对话框界面的时候)。
这篇指南的以下章节介绍了如何把 DialogFragment
与 AlertDialog
结合使用。如果你想要创建日期或时间的选择器,你应该去阅读 Pickers 指南。
注解: 因为 DialogFragment
类直到Android 3.0(API等级11)才被引入,所以这篇文档介绍的是在拥有 Support Library 时使用 DialogFragment
类。通过把该类库添加到你的应用中,你能在运行Android 1.6或更高版本的设备上使用 DialogFragment
或其他种种API。 如果你的应用支持的最低版本是API等级11或更高,那么你可以直接使用框架版本的DialogFragment
,但是请注意这篇文档里的链接使用的都是支持库的API。使用支持库前,请确保你引入了 android.support.v4.app.DialogFragment
类而不是android.app.DialogFragment
。
创建对话框碎片
通过继承 DialogFragment
并且在它的 onCreateDialog()
回调方法里创建一个 AlertDialog
,你能完成包含自定义布局和那些在Dialogs 设计指南中描述过的的各种各样的对话框设计。
例如,这里有一个在 DialogFragment
内部被管理的基本的 AlertDialog
:
public class FireMissilesDialogFragment extends DialogFragment {
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
// 使用Builder类方便的构建对话框
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) {
// 用户取消对话框
}
});
// 创建 AlertDialog 对象并返回
return builder.create();
}
}
图1. 一个带有一条消息和两个操作按钮的对话框。
现在,当你创建这个类的实例并且调用了这个对象的 show() 方法,像图1中展示的对话框就会显示出来。
下一节介绍了更多关于使用 AlertDialog.Builder
API去创建对话框的知识。
根据你的对话框的复杂程度,你可以继承 DialogFragment 里其他的多种多样的回调方法,比如所有基础的 fragment lifecycle methods。
构建警告对话框
通常你仅仅需要使用 AlertDialog
类就能构建多种多样的对话框设计。如图2所示,一个警告对话框有三个区域:
图2. 对话框的布局。
- 标题
这是可选的并且应该只有当内容区被一条详细的消息、一个列表或自定义布局填充时才被使用。如果你只需要声明一条简单的消息或问题(如图1的对话框),那么你不需要使用标题。
- 内容区
这里可以显示一条消息,一个列表或其他自定义布局。
- 操作按钮
在对话框里最多只能显示3个操作按钮。
使用 AlertDialog.Builder
类提供的API,你可以构建一个包含这些内容甚至自定义布局的警告对话框。
构建警告对话框的步骤:
// 1. 用构造方法初始化一个 AlertDialog.Builder
实例
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
// 2. 链接上各种各种的设置方法来设置对话框的特征
builder.setMessage(R.string.dialog_message)
.setTitle(R.string.dialog_title);
// 3. 通过 create()
方法获取 AlertDialog
实例
AlertDialog dialog = builder.create();
接下来的专题将会展示通过使用 AlertDialog.Builder
类如何定义各种各样的对话框属性。
添加按钮
调用 setPositiveButton()
和 setNegativeButton()
方法来添加如图2里的操作按钮。
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
// 添加按钮
builder.setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
// 用户点击OK按钮
}
});
builder.setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
// 用户点击取消按钮
}
});
// 这是对话框的其他属性
...
// 创建警告对话框
AlertDialog dialog = builder.create();
这些 set...Button()
方法需要为按钮设置一个标题(由字符串资源提供),还需要实现 DialogInterface.OnClickListener 接口以便在用户按下按钮时响应。
你可以添加三种不同的操作按钮:
-
积极性质的
- 你应该使用这种按钮来接收和继续执行操作("OK"操作)。 消极性质的
- 你应该使用这种按钮来取消操作。 中立性质的
- 当用户可能不想继续这个操作但是又不想取消它时你应该使用这种按钮。它显示在积极按钮和消极按钮之间。例如:这个操作可能叫做“稍后提醒”。
对于每种类型的按钮,你只能添加一个到警告对话框中。也就是说,警告对话框中不能出现多余一个的“积极”按钮。
图3. 一个带标题和列表的对话框。
添加列表
AlertDialog API提供了三种可用的列表:
- 传统的单选列表
- 持久的单选列表(单选按钮)
- 持久的多选列表(复选框)
使用 setItems()
方法来创造如图3所示的单选列表:@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) {
// ‘which’参数包含被选中项目的索引
}
});
return builder.create();
}
由于列表也是在对话框的内容区里呈现的,所以对话框不能同时显示消息和列表,你只能使用 setTitle() 方法设置个标题。调用 setItems() 方法,传递一个数组参数,就能指明列表的项目了。或者,你也可以使用 setAdapter() 方法来指定列表。使用 ListAdapter 你能用动态数据(例如来自数据库)来填充列表。
如果你选择使用 ListAdapter 来填充你的列表,为了能异步加载内容请使用 Loader。在 Building Layouts with an Adapter 和 Loaders 指南中有更详细的描述。
注解: 默认情况下,触摸一个列表选项后就会关闭对话框,除非你使用的是下面持久的选项列表中的一种。
图4. 多选列表。
添加持久的多选或单选列表
可以分别使用 setMultiChoiceItems()
和 setSingleChoiceItems()
方法来添加多选列表(复选框)和单选列表(单选按钮)。
例如,下面的例子展示了如何创建一个如图4的多选列表并把选中的项目保存在一个 ArrayList中:
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
mSelectedItems = new ArrayList(); // 追踪选中项目的数组
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
// 设置对话框标题
builder.setTitle(R.string.pick_toppings)
// 指定数组列表,默认所有项目都未选中,当项目被选中时通过监听器可以接收到回调
.setMultiChoiceItems(R.array.toppings, null,
new DialogInterface.OnMultiChoiceClickListener() {
@Override
public void onClick(DialogInterface dialog, int which,
boolean isChecked) {
if (isChecked) {
// 如果用户选中,把选中项添加到选中项目的数组中
mSelectedItems.add(which);
} else if (mSelectedItems.contains(which)) {
// 否则,如果项目已经在数组中了,那么移除它
mSelectedItems.remove(Integer.valueOf(which));
}
}
})
// 设置操作按钮
.setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int id) {
// 用户点击OK后,在某处保存选中项目的结果或返回给打开这个对话框的组件
...
}
})
.setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int id) {
...
}
});
return builder.create();
}
虽然传统的列表和带单选按钮的列表都提供了单选操作,但是如果你想持久的保存用户的选择你应该使用 setSingleChoiceItems() 方法。也就是说,如果以后再打开这个对话框,应该指明用户的当前选中是什么,然后你才能创建单选按钮的列表。
创建自定义布局
图5. 自定义布局的对话框。
如果你想在对话框中使用自定义布局,创建布局并调用 AlertDialog.Builder
对象的 setView() 方法把自定义布局添加到警告对话框中。
默认情况下,自定义布局文件填充整个对话框窗口,然而你还是可以使用 AlertDialog.Builder
的方法为对话框添加标题和按钮。
例如,下面是图5所示对话框的布局文件:
res/layout/dialog_signin.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<ImageView
android:src="@drawable/header_logo"
android:layout_width="match_parent"
android:layout_height="64dp"
android:scaleType="center"
android:background="#FFFFBB33"
android:contentDescription="@string/app_name" />
<EditText
android:id="@+id/username"
android:inputType="textEmailAddress"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:layout_marginLeft="4dp"
android:layout_marginRight="4dp"
android:layout_marginBottom="4dp"
android:hint="@string/username" />
<EditText
android:id="@+id/password"
android:inputType="textPassword"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:layout_marginLeft="4dp"
android:layout_marginRight="4dp"
android:layout_marginBottom="16dp"
android:fontFamily="sans-serif"
android:hint="@string/password"/>
</LinearLayout>
小贴士: 当你把一个 EditText 元素的输入类型设置为"textPassword"时,字体属性将被更改成monospace,所以为了让所有的输入框使用同样的字体风格你应该把它的字体属性更改为"sans-serif"。
使用 getLayoutInflater()
方法获取一个 LayoutInflater 实例,然后可以调用它的 inflate() 方法在你的 DialogFragment
中扩充布局,第一个参数是布局的资源ID,第二个参数是布局的父视图。然后你就能调用 setView() 方法把布局放置到对话框中。
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
// 获取layout inflater
LayoutInflater inflater = getActivity().getLayoutInflater();
// 填充并设置对话框
// 因为它的行为发生在对话框布局中,所以把它的父视图置为空
builder.setView(inflater.inflate(R.layout.dialog_signin, null))
// 添加操作按钮
.setPositiveButton(R.string.signin, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int id) {
// 用户登录...
}
})
.setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
LoginDialogFragment.this.getDialog().cancel();
}
});
return builder.create();
}
小贴士: 如果你想要一个自定义对话框,你还可以把一个 Activity 作为成对话框来使用,而不是使用 Dialog API。非常简单的,创建一个activity并在清单里 <activity>
元素中把它的主题设置为 Theme.Holo.Dialog
。
<activity android:theme="@android:style/Theme.Holo.Dialog" >
就是这样,现在activity不再全屏显示了,而在一个对话框窗口中显示。
事件回传给对话框的宿主
当用户触碰对话框的任一动作按钮或选中列表里的一个选项,也许你的 DialogFragment 自己会执行一些必要的处理,但是通常你会希望将事件传递给打开这个对话框的activity或fragment。为此,定义一个接口并且为每种点击事件定义各自的方法。然后在宿主组件中继承这个接口,这样宿主就能接收到来自对话框的动作事件。
例如,下面的 DialogFragment
定义了一个通过它可以把事件回传给宿主activity的接口。
public class NoticeDialogFragment extends DialogFragment {
/*为了接收到事件的回调,创建对话框的activity必须继承这个接口。
* 以防宿主需要查询对话框的属性,每个方法都将传递一个DialogFragment实例。 */
public interface NoticeDialogListener {
public void onDialogPositiveClick(DialogFragment dialog);
public void onDialogNegativeClick(DialogFragment dialog);
}
// 使用这个接口的实例来传递动作事件
NoticeDialogListener mListener;
// 重写Fragment.onAttach()方法来实例化NoticeDialogListener
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
// 验证宿主acrivity是否继承回调接口
try {
// 实例化NoticeDialogListener以便我们能向宿主传递事件
mListener = (NoticeDialogListener) activity;
} catch (ClassCastException e) {
// activity没有继承接口则抛出异常
throw new ClassCastException(activity.toString()
+ " must implement NoticeDialogListener");
}
}
...
}
托管对话框的activity使用DialogFragment的构造方法来实例化对话框,并且通过继承NoticeDialogListener接口可以接收到对话框的事件:
public class MainActivity extends FragmentActivity
implements NoticeDialogFragment.NoticeDialogListener{
...
public void showNoticeDialog() {
// 创建DialogFragment的实例并显示
DialogFragment dialog = new NoticeDialogFragment();
dialog.show(getSupportFragmentManager(), "NoticeDialogFragment");
}
// 通过Fragment.onAttach()的回调,DialogFragment的实例可以接收到那个被用来实现下面NoticeDialogFragment.NoticeDialogListener定义的方法的引用
@Override
public void onDialogPositiveClick(DialogFragment dialog) {
// 用户触碰对话框的积极按钮
...
}
@Override
public void onDialogNegativeClick(DialogFragment dialog) {
// 用户触碰对话框的消极按钮
...
}
}
因为宿主activity继承了NoticeDialogListener接口并且如上所示可以在 onAttach() 中被强制转换,所以DialogFragment可以使用接口的回调方法把点击事件传递给activity。
public class NoticeDialogFragment extends DialogFragment {
...
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
// 构建对话框并设置按钮点击事件处理器
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) {
// 把积极按钮点击事件回传给宿主activity
mListener.onDialogPositiveClick(NoticeDialogFragment.this);
}
})
.setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
// 把消极按钮点击事件回传给宿主activity
mListener.onDialogNegativeClick(NoticeDialogFragment.this);
}
});
return builder.create();
}
}
显示对话框
当你想要显示你的对话框时,实例化你的 DialogFragment 对象并传递 FragmentManager 和标签名给它的 show()
方法,调用该方法即可。
你可以通过调用 FragmentActivity
的 getSupportFragmentManager() 或 Fragment
的 getFragmentManager() 方法获取 FragmentManager
实例。例如:
public void confirmFireMissiles() {
DialogFragment newFragment = new FireMissilesDialogFragment();
newFragment.show(getSupportFragmentManager(), "missiles");
}
第二个参数, ”missiles”
是一个唯一的标签名,它是为了让系统在必要的时候用来保存和恢复fragment状态的。这个标签同样也允许你通过调用 findFragmentByTag()
来获取fragment的句柄。
显示全屏或嵌入式碎片化的对话框
你可能需要这样一种界面设计,你希望界面的一部分在某些情况下显示为对话框,并且在某些情况(可能依赖于设备是大屏幕或小屏幕)下对话框会显示为全屏的或嵌入式碎片化的对话框。因为DialogFragment
类始终相当于嵌入式的 Fragment,所以它能为你的设计提供了灵活性。
然而,如果你想要 DialogFragment
成为可嵌入的,那么你不能使用 AlertDialog.Builder
或其他 Dialog
对象去构建对话框,你必须在布局文件中定义对话框的用户界面,然后在 onCreateView()
回调中加载。
下面例子的 DialogFragment
可以显示为一个对话框或嵌入式的碎片(使用名为purchase_items.xml的布局):
public class CustomDialogFragment extends DialogFragment {
/** 不管DialogFragment显示为对话框或嵌入式的碎片,系统调用该方法去扩充它的布局。 */
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// 扩充布局以使它作为对话框或嵌入式的碎片使用
return inflater.inflate(R.layout.purchase_items, container, false);
}
/** 系统仅在对话框中创建布局时调用。 */
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
// 当使用onCreateView()后,你可能重写该方法的唯一原因是修改对话框的特征。
// 例如,默认情况下对话框包含一个标题,但是你的自定义布局可能不需要它。
// 所以在调用超类方法获取对话框对象后,你就可以移除对话框的标题了。
Dialog dialog = super.onCreateDialog(savedInstanceState);
dialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
return dialog;
}
}
下面的代码基于屏幕的尺寸就能决定什么时候显示碎片化的对话框,什么时候显示全屏的界面:
public void showDialog() {
FragmentManager fragmentManager = getSupportFragmentManager();
CustomDialogFragment newFragment = new CustomDialogFragment();
if (mIsLargeLayout) {
// 设备使用大型布局,所以把它作为对话框显示
newFragment.show(fragmentManager, "dialog");
} else {
// 设备屏幕很小,所以全屏显示
FragmentTransaction transaction = fragmentManager.beginTransaction();
// 为了更加完美,指定过渡动画
transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
// 为了达到全屏效果,使用经常作为activity根视图的名为‘content’的根视图作为fragment的容器
transaction.add(android.R.id.content, newFragment)
.addToBackStack(null).commit();
}
}
更多关于碎片表现效果的信息,请参考 Fragments 指南。
在这个例子里,布尔变量mIsLargeLayout
指明了当前设备是否应该使用应用的大型布局的设计(这样显示碎片化的对话框,而不是全屏的) 。设置这种布尔变量的最好的方式是为不同屏幕大小在 alternative resource 声明不同的 bool resource value。
res/values/bools.xml
<!-- 默认布尔值 -->
<resources>
<bool name="large_layout">false</bool>
</resources>
res/values-large/bools.xml
<!-- 大屏布尔值 -->
<resources>
<bool name="large_layout">true</bool>
</resources>
现在你可以在activity的 onCreate()
方法期间初始化mIsLargeLayout
变量的值:
boolean mIsLargeLayout;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mIsLargeLayout = getResources().getBoolean(R.bool.large_layout);
}
在大屏幕上显示对话框式的活动
像在小型屏幕上显示一个全屏对话框效果一样,在大型屏幕上你可以通过把一个 Activity 当做对话框展示来得到同样的结果。依赖于你应用的设计,当你的应用已经为小型屏幕设计过时,通过把一个短暂存在的activity作为对话框显示对于提升平板上用户体验是非常有用的。
在清单里 <activity>
元素中应用 Theme.Holo.DialogWhenLarge 主题就能实现仅当在大型屏幕上把activity当做对话框显示的效果。
<activity android:theme="@android:style/Theme.Holo.DialogWhenLarge" >
更多关于activity使用主题样式信息,请参考 Styles and Themes 指南。
解散对话框
当用户触碰到通过 AlertDialog.Builder
创建的任何一个操作按钮时,系统将会为你解散这个对话框。
当用户触碰到对话框列表中的项目时系统同样会解散这个对话框,除非这是一个拥有单选按钮或复选框列表的对话框。另外,你可以通过调用 DialogFragment 里的 dismiss() 方法来手动解散你的对话框。
如果你需要在对话框消失时执行某些操作,你可以在你的 DialogFragment 类中继承 onDismiss()
方法。
你也可以取消一个对话框。这是一种指明用户在没有完成任务时就要明确的要求离开的特别的事件。如果用户点击返回按钮,触碰屏幕上对话框区域之外的地方,或者你明确的调用 Dialog
的 cancel() 方法时(例如响应对话框的“取消”按钮)。
就像上面列子里展示的,你可以在你的 DialogFragment 类中继承 onCancel()
方法来响应取消事件。
注解: 虽然系统在每次调用 onDismiss()
之前会先调用 onCancel()
回调,但是如果你直接调用的是 Dialog.dismiss()
或 DialogFragment.dismiss()
方法的话系统仅触发 onDismiss()
而不会触发 onCancel()
。所以一般而言当用户按下你对话框里的积极按钮时,你应该调用 dismiss()
方法把对话框从视图中移除。