Android UI 之 Dialog 使用
引言
在现在的应用程序中,Dialog 的应用十分广泛。常用于提示用户一些信息。
在应用程序中使用 Dialog
在 Android 系统中有多种自带的 Dialog 供我们使用,我们不需要再去自己动手实现它。
- AlertDialog 是一个可以显示标题、内容、最多三个按钮,也可以显示普通列表、单选列表、多选列表或者自定义空间的多功能 Dialog。
- DatePickerDialog 和 TimePickerDialog 用于显示。
安卓系统已经为我们定义好了这些 Dialog 的样式和结构,我们只需要调用相应的方法就行。
Dialog 是所有 dialogs 的基类,我们可以直接使用 Dialog 创建 dialogs。然而 Android 官方建议我们使用 DialogFragment 类而不是直接使用 Dialog 来创建 dialogs, DialogFragment 类提供了你创建 Dialog 并控制其外观的所有方法。并且使用 DialogFragment 去管理 Dialog 能够确保 Dialog 正确地处理其生命回收的事件。
下面演示如何创建一个带有标题、内容和两个按钮的 Dialog。首先,继承 DialogFragment 并重写其 onCreateDialog() 方法:
public class TraditionalDialogFragment extends DialogFragment { @Override public Dialog onCreateDialog(Bundle savedInstanceState) { // 创建 AlertDialog 对象 AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); // 为 Dialog 对象设置标题、内容、按钮。 builder.setTitle("title") .setMessage("This is Message!") .setPositiveButton("Positive", new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int id) { // do something } }) .setNegativeButton("Negative", new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int id) { // do something } }); // 新建一个 Dialog 并返回给调用者 return builder.create(); } }
然后,我们在 Activity 中,创建一个 DialogFragment 并调用其中的 show() 方法以显示 Dialog :
DialogFragment newFragment = new TraditionalDialogFragment(); newFragment.show(getFragmentManager(), "DialogTest");
show() 方法的作用是显示一个 Dialog,并把 Fragment 交给我们提供的(通过 getFragmentManager() 方法获取的)FragmentManager 中。
其中 “DialogTest”是系统在必要时用于保存和恢复 Fragment 状态的一个独一无二的标记。
AlertDialog 的几种类型
前面说过 AlertDialog 有好几种类型,这里分别介绍:
普通类型
通过调用 setTitle() 方法为 Dialog 设置标题:
builder.setTitle("title");
当然,也可以从资源文件中加载标题。假设已经存在一个名为 dialog_title 的 string 资源:
builder.setTitle(R.string.dialog_title);
下面,为 Dialog 设置显示的正文:
builder.setMessage("This is Message!");
因为其返回类型为 Builder,你也可以直接这样使用:
builder.setTitle("title") .setMessage("This is Message!");
当然也可以使用资源。Dialog 有最多有三个按钮:
- Positive 这个便是我们常见的“OK、继续”等按钮。
- Negative 主要是我们平常用的“Cancel”。
- Neutral 这个按钮在 Positive 和 Negative 中间,常用于“稍后提醒”
我们可以同过下面的方式添加按钮并且处理相应的事件:
builder.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int id) { } });
其中,第一个参数用于设置显示在按钮上的文字,这里我使用了 Android 自身自带的 cancel。后一个参数辨识为按钮设置相应的点击事件代码。如果想用其他两种按钮,直接将其中的 Negative 换成其他的。
普通的 List
当我们需要使弹出菜单类变为一个 ListDialog 时,我们也可以直接使用 AlertDialog 为我们提供的 List。
我们可以通过如下方法为 Dialog 增加 list 的 Item:
builder.setItems(android.R.array.emailAddressTypes, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { } });
setItems 为 Dialog 增加一系列的数据用于 List。第一个参数为相应的数据,我在这里使用的是 Android 自带的一个数组数据,这个也可以自己定义。第二个参数这是 List 相应用户操作的代码,which 表示用户选择的项的索引。当然,你也可以通过 setAdapter() 方法传入一个 ListAdapter 来动态的为 List 添加数据。
需要注意的是:List 占满了 Dialog 内容部分,所以不能同时显示 Message 和 List。并且,用户一旦点击 List 中某一个 Item,便会触发销毁 Dialog 的事件,所以没有任何必要为其添加 Button(如果你愿意也可以)。
单选 List
单选List在 Item 后面显示一个单选按钮(radio buttons):
这个和普通的 List 基本上一样,但是不会像普通 List 一样在点击 item 后,触发销毁事件,所以还是需要我们提供按钮来销毁 Dialog 。添加数据也和普通 List 差不多,只需要将 setItems 换成 setSingleChoiceItems()。
多选 List
有时候需要同时选中多个数据,这时候就可以使用 MultipleList:
使用 MultipleDialog 只需要将 SingleList 中的 setSingleChoiceItem 换成下面的代码就行:
builder.setMultiChoiceItems(android.R.array.emailAddressTypes, null, new DialogInterface.OnMultiChoiceClickListener() { @Override public void onClick(DialogInterface dialog, int which, boolean isChecked) { // do something } })
其中 isChecked 表示的是是否被选择,因为我们可能需要记录下用户的选择,这便是一个不错的途径。我们可以用一个 ArrayList 来储存用户选择的项:
ArrayList mSelectedItems = new ArrayList();
并在 onClick 中加入下面这段代码来记录数据:
if (isChecked) { // If the user checked the item, add it to the selected items mSelectedItems.add(which); } else if (mSelectedItems.contains(which)) { // Else, if the item is already in the array, remove it mSelectedItems.remove(Integer.valueOf(which)); }
提示:对于这种可以反馈用户选择信息的控件(Mulitiple、Single),有时可能需要在初始化的时候就选中一些用户原始的数据。
自己定义的 Dialog
有时候需要在 Dialog 中实现一个登陆界面,但是 Android 中并没有提供现成的 Dialog 给我们使用,这时候 CustomDialog 便派上用场了。 AlertDialog 中间的内容部分允许用户自己定义 Layout,通过 setView() 添加到 AlertDialog 中:
默认情况下,设置的 Layout 的会充满整个 Dialog,但是我们依然可以为其添加 Title 和 Button。以一个登陆界面为例演示如何使用 CustomDialog。
首先,得加入布局文件Layout:
<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/head_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/dialog_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/dialog_password" 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:hint="@string/password" android:inputType="textPassword" /> </LinearLayout>
通过 inflater 在 Fragment 中创建 View:
LayoutInflater inflater = getActivity().getLayoutInflater(); // 因为是在 Dialog 中,父视图填充 null。 View view = inflater.inflate(R.layout.dialog_signin, null); builder.setView(view);
然后在添加按钮:
builder.setPositiveButton("signin", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int id) { // sign in the user ... } }); builder.setNegativeButton("cancel", new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int id) { // cancel ... } });
这样,便实现登陆框。
将数据返回给 Dialog 的持有者
当用户在改变数据并点击确认的时候需要将数据返回给当前 Dialog 的持有者。要完成这个功能,首先得定义一个包含各种类型的点击事件的接口。
public interface NoticeDialogListener { public void onDialogPositiveClick(Object object); public void onDialogNegativeClick(Object object); // ... }
然后,让 Activity 继承并实现相应的方法:
public class MainActivity extends Activity implements NoticeDialogListener { // ..... @Override public void onDialogPositiveClick(Object o) { // do something } @Override public void onDialogNegativeClick(Object o) { // do something } // .... }
在我们的 DialogFragment 中声明一个 NoticeDialogListener 变量。
NoticeDialogListener mListener;
现在在 DialogFragment 中重写 onAttach() 方法:
@Override public void onAttach(Activity activity) { super.onAttach(activity); // Verify that the host activity implements the callback interface try { // Instantiate the NoticeDialogListener so we can send events to the host mListener = (NoticeDialogListener) activity; } catch (ClassCastException e) { // The activity doesn‘t implement the interface, throw exception throw new ClassCastException(activity.toString() + " must implement NoticeDialogListener"); } }
onAttach() 将我们传给 DialogFragment 的 Activity 对象中的 NoticeDialogListener 部分保存下来,用于稍后返回数据时调用。现在在相应的事件中添加相应的代码:
builder.setTitle("title") .setMessage("This is Message!") .setPositiveButton("Positive", new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int id) { mListener.onDialogPositiveClick(/* 加入要返回的数据 */); } }) .setNegativeButton("Negative", new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int id) { mListener.onDialogNegativeClick(/* 加入要返回的数据 */); } });
这样便实现了将数据返回到 Dialog 的持有者那儿。当然,如果一个程序中使用了使用了多种 Dialog,都需要返回数据,那么在我们实现的 onDialogPositiveClick 方法中,怎样判断数据源?方法很多,可以为每一种 Dialog 都写一种方法,这样不仅重复并且效率低下。或者改写 NoticeDialogListener :
public interface NoticeDialogListener { public void onDialogPositiveClick(int type, Object object); public void onDialogNegativeClick(int type, Object object); // ... }
并在实现该方法地方使用 switch 来匹配信息:
public void onDialogPositiveClick(int type, Object o) { switch (type) { case 1: // traditional break; case 2: // list break; // ... default: break; } }
这样便实现了将数据回传给 Dialog 的持有者。
Dialog 的销毁
如果是自定义的 Dialog 而没有加入系统自带的按钮时,需要我们自己实现 Dialog 的销毁。Andorid 系统允许我们在 DialogFragment 中使用 dismiss() 方法销毁当前的 Dialog。也有方法也可以执行销毁工作,比如当 Dialog 正在工作时用户点击了 Back 按键,或者代码中直接调用 onCancel() 方法,这就像点击了 Dialog 中的 Cancel 按钮一样。