现在大多数 App,只要不是纯 Material Design 风格的 App,基本都会对 Dialog 的样式进行一些自定义。一方面可能是因为美工只针对 iOS 做了一套设计,而 Android 就只能跟随这套设计走,另一方面,对 Dialog 样式的自定义也是为了能够在各场景下有合理的应用。

之前『Android 使用 Dialog 样式的 Activity』一文中利用设置 Activityandroid:theme 属性来仿造了一个自定义样式的 Dialog,但其实际本质仍然是一个 Activity,且如果在每个需要自定义 Dialog 的场景都使用这种方式,无疑是大材小用,并且会使 App 变得笨重。这也是 Dialog 这个控件存在的意义。

那么该如何自定义 Dialog 的样式呢?

其实并不难,Android 中的样式大多数情况都是可以根据 Layout 来布局的,如果没有深度的自定义 View 绘制需求,使用自带的控件组合就可以轻易的帮我们完成自定义的 Dialog 布局。

以之前『Android 解码 Base64 图片』一文中的验证码需求为示例,可以写出如下简单的布局:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">
    <ImageView
        android:id="@+id/code_view"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:layout_marginTop="24dp"
        android:src="@drawable/loading" />
    <EditText
        android:id="@+id/code_edit"
        android:layout_width="256dp"
        android:layout_height="wrap_content"
        android:layout_margin="12dp"
        android:layout_gravity="center"
        android:inputType="number"
        android:maxLength="4"
        android:gravity="center" />
</LinearLayout>

ImageView 用于展示图形验证码,EditText 用于接收用户输入的验证码,很好理解吧。由于图片是通过网络获取的,所以这里默认图我给了一张 Loading 的图片,等通过网络请求到实际的验证码图片时再替换;至于输入框,则加入了最大长度以及输入类型等限制,这里涉及到具体的业务逻辑,不做过多解释。

不难发现,对于 Dialog 的样式定制,其实跟我们平时写 Activity 的布局相差无几。

通过『Android Studio』的实时布局预览面板,也可以像平时写布局一样预览到效果:

布局预览

在自定义 Dialog 时,我们需要将对应的布局找到,并且绑定到对应 Dialog 中:

View imageCodeDialog = getLayoutInflater().inflate(R.layout.image_code_layout, null);
ImageView codeView = (ImageView) imageCodeDialog.findViewById(R.id.code_view);
EditText codeEdit = (EditText) imageCodeDialog.findViewById(R.id.code_edit);
...
new AlertDialog.Builder(this)
        .setView(imageCodeDialog)
        .setPositiveButton("提交", new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                …
            }
        })
        .setCancelable(false)
        .show();

可以看到,需要先通过 inflate() 方法获取到刚刚的布局文件后,才可以通过 findViewById() 来绑定对应的控件,在这里你可以将 inflate() 理解为类似控件中 findViewById() 方法所做的操作,只不过 findViewById() 绑定的是控件 ID,而 inflate() 绑定的是布局文件。

获取到对应的控件实例后就可以跟平时一样对其做事件响应了,这里不是重点,省略。

当你把事件响应都编写完后,最重要的弹出 Dialog 步骤来了,其实跟一般情况下弹出 Dialog 区别也不大,只不过是在 show() 方法执行之前,调用 setView() 方法将通过 inflate() 绑定的 View 设置进来就可以了,setView() 也可以理解为类似 Activity 中的 setContentView() 方法所做的操作。

效果如下:

运行效果