AlertDialog自定义

## 简介

AlertDialog是每一个App所必须的控件,在Android4.0之前不可否认官方正版的Dialog很不美观,但是Android4.0之后的holo风格的Dialog已经可以上的了厅堂了,再后来的Material Design设计风格的拟物扁平化Dialog已经是给人耳目一新的感觉。虽然官方UI标准愈加完善,可是部分UI设计师依然是按照苹果iOS的规范设计App的UI,完全忽略不同平台UI包括交互上的差异性,作为开发者也只好苦逼的遵循UI设计的规范,结果就是Android版本的Dialog多数情况下是以自定义UI风格的方式呈现。系统官方的AlertDialog使用了建造者模式,并且可以链式调用相当方便,所以这次从系统中剥离了AlertDialog源码并进行部分改动以便适用于不同风格。

自定义AlertDialog支持如下方式:

- 系统AlertDialog怎么使用该AlertDialog就怎么使用;

- 支持自定义样式和主题;

- 支持自定义AlertDialog布局文件;

- 支持自定义单选复选列表弹框;

- 支持功能扩展。

## 一些常用的方法

以下是一些常用的方法:

```java

Builder ()

构造方法,默认一个参数的是Theme.Holo.Light主题,可以说设置两个参数,第二个参数可以传入主题

setTitle ()

设置标题栏文本;

setIcon ()

设置标题栏icon;

setIconAttribute ()

设置特定主题下的icon;

setCustomTitle ()

设置自定义标题栏;

setMessage ()

设置弹框内容区域文本;

setItems ()

设置弹框列表;

setSingleChoiceItems ()

设置单选模式列表;

setMultiChoiceItems ()

设置复选模式列表;

setNegativeButton()

设置左侧按钮;

setNeutralButton ()

设置中间按钮;

setPositiveButton ()

设置右侧按钮;

setView ()

设置自定义内容区域View;

```

AlertDialog的基本使用

创建一个标准AlertDialog:

```java

new Builder(this)

.setTitle("title")

.setMessage("hello world")

.setPositiveButton("确定", new OnClickListener() {

@Override

public void onClick(DialogInterface dialog, int which) {

dialog.dismiss();

}

}).setNeutralButton("中间", new OnClickListener() {

@Override

public void onClick(DialogInterface dialog, int which) {

dialog.dismiss();

}

}).setNegativeButton("取消", new OnClickListener() {

@Override

public void onClick(DialogInterface dialog, int which) {

dialog.dismiss();

}

}).show();

```

创建一个无标题栏AlertDialog:

只需要将上述标准Dialog中setTitle方法剔除就可以了。示例代码如下:

```java

new Builder(this)

.setMessage("hello world")

.setPositiveButton("确定", new OnClickListener() {

@Override

public void onClick(DialogInterface dialog, int which) {

dialog.dismiss();

}

}).setNeutralButton("中间", new OnClickListener() {

@Override

public void onClick(DialogInterface dialog, int which) {

dialog.dismiss();

}

}).setNegativeButton("取消", new OnClickListener() {

@Override

public void onClick(DialogInterface dialog, int which) {

dialog.dismiss();

}

}).show();

```

```java

protected AlertDialog(Context context) {

this(context, 0);

}

protected AlertDialog(Context context, int theme) {

super(context, resolveDialogTheme(context, theme));

mAlert = new AlertController(context, this, getWindow());

}

static int resolveDialogTheme(Context context, int resId) {

if (resId == 0) {

return R.style.DialogHoloLight;

} else {

return resId;

}

}

```

首先创建一个深色主题继承一个默认系统已有的深色Dialog主题。代码如下:

```xml

```

这里我们将dialog的布局文件也重新设置了一个,并且设置了该主题下标题栏icon。

要创建一个类似iOS风格的AlertDialog,首先需要创建一个自定义的Builder类,然后设置相应的样式和属性。以下是一个简单的示例:

1. 创建一个名为`AlertDialogBuilder`的自定义Builder类,继承自`AlertDialog.Builder`,并设置主题为iOS风格:

```java

import android.app.AlertDialog;

import android.content.Context;

import android.content.DialogInterface;

import androidx.annotation.NonNull;

import androidx.core.content.ContextCompat;

public class AlertDialogBuilder extends AlertDialog.Builder {

private Context context;

private int themeResId;

public AlertDialogBuilder(@NonNull Context context) {

super(context);

this.context = context;

this.themeResId = R.style.AlertDialogDark; // 这里设置你的iOS风格主题资源ID

}

@Override

public void setTitle(int titleResId) {

super.setTitle(titleResId);

}

@Override

public void setMessage(CharSequence message) {

super.setMessage(message);

}

@Override

public AlertDialog build() {

AlertDialog alertDialog = super.build();

alertDialog.getWindow().setBackgroundDrawableResource(R.drawable.dialog_background); // 这里设置你的背景图片资源ID

return alertDialog;

}

}

```

2. 在你的Activity或Fragment中使用`AlertDialogBuilder`创建一个类似iOS风格的AlertDialog:

```java

private void showAlertDialog() {

Context ctx = this; // 如果你在Fragment中使用,请替换为getContext()方法返回的对象

AlertDialogBuilder builder = new AlertDialogBuilder(ctx);

builder.setTitle("标题")

.setMessage("这是一个类似iOS风格的AlertDialog")

.setPositiveButton("确定", new DialogInterface.OnClickListener() {

@Override

public void onClick(DialogInterface dialog, int which) {

dialog.dismiss();

}

})

.setNeutralButton("中间", new DialogInterface.OnClickListener() {

@Override

public void onClick(DialogInterface dialog, int which) {

dialog.dismiss();

}

})

.setNegativeButton("取消", new DialogInterface.OnClickListener() {

@Override

public void onClick(DialogInterface dialog, int which) {

dialog.dismiss();

}

});

AlertDialog alertDialog = builder.build(); // 这将应用你设置的主题和其他属性到新创建的AlertDialog上

alertDialog.show(); // 最后显示这个AlertDialog

}

```

注意:这个示例仅仅是一个基本的实现,你可能需要根据你的需求进行更多的定制,例如添加圆角布局、从底部滑入动画等。

```xml

```

AlertDialog的创建方式跟上面深色主题类似,只需要在构造方法传入主题即可:

```java

new Builder(this, R.style.DialogIOS) ...

```

如果创建类似QQ从底部滑入动画,只需要在主题中设置一个转场动画:

```xml

```

创建一个列表AlertDialog:

```java

List items = new ArrayList<>();

items.add("Item 1");

items.add("Item 2");

items.add("Item 3");

new AlertDialog.Builder(this, R.style.DialogIOS)

.setTitle("选择一个项目")

.setItems(items.toArray(new String[0]), new DialogInterface.OnClickListener() {

public void onClick(DialogInterface dialog, int which) {

// Handle click on an item

}

})

.setPositiveButton("确定", null)

.show();

```

创建列表对话框时,包括单选和多选模式实际上都是单行文本模式列表。如果想要支持更复杂列表,可以更改源码或者传入一个自定义View。以下是创建列表对话框的示例代码:

```java

new Builder(this)

.setTitle("title")

.setIconAttribute(android.R.attr.alertDialogIcon)

.setItems(getData(), new OnClickListener() {

@Override

public void onClick(DialogInterface dialog, int position) {

dialog.dismiss();

}

})

.show();

```

要创建单复选列表AlertDialog,首先需要使用自定义布局方式来更改系统内置默认的布局。这里我们使用了CheckedTextView控件,因为其checkMark属性可以用来设置单复选图标。接下来定义一个选择器btn_radio_right,如下所示:

```xml

```

最后,定义一个单选模式的布局文件custom_dialog_singlechoice.xml:

```xml

android:layout_width="match_parent"

android:layout_height="wrap_content"

android:orientation="horizontal">

android:id="@+id/checkbox"

android:layout_width="24dp"

android:layout_height="24dp"

android:src="@drawable/ic_radio_button_off" />

android:id="@+id/textview"

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:text="选项内容" />

```

复选模式的布局在实现上与单选模式类似,主要区别在于添加了一个复选框(checkbox)来表示选中状态。以下是一个简单的复选模式布局实现示例:

```xml

android:layout_width="match_parent"

android:layout_height="wrap_content">

android:id="@android:id/text1"

android:layout_width="match_parent"

android:layout_height="wrap_content"

android:minHeight="?android:attr/listPreferredItemHeightSmall"

android:textAppearance="?android:attr/textAppearanceMedium"

android:textColor="?android:attr/textColorAlertDialogListItem"

android:gravity="center_vertical"

android:paddingStart="16dip"

android:paddingEnd="16dip"

android:checkMark="@drawable/btn_radio_light"

android:ellipsize="marquee" />

android:id="@+id/checkbox"

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:layout_alignParentRight="true"

android:layout_marginRight="16dp" />

```

在这个示例中,我们使用`RelativeLayout`作为根布局,然后添加一个`CheckedTextView`作为文本项和一个`CheckBox`作为复选框。通过调整布局参数,可以实现复选模式的布局效果。

设置自定义AlertDialog的代码如下:

```java

// 单选模式

new Builder(this)

.setTitle("标题")

.setIconAttribute(android.R.attr.alertDialogIcon)

.setSingleChoiceItems(getData(), 0, new OnClickListener() {

@Override

public void onClick(DialogInterface dialog, int position) {

dialog.dismiss();

}

})

.show();

// 复选模式

new Builder(this)

.setTitle("标题")

.setIconAttribute(android.R.attr.alertDialogIcon)

.setMultiChoiceItems(getData(), null, new OnMultiChoiceClickListener() {

@Override

public void onClick(DialogInterface dialog, int position, boolean isChecked) {

}

})

.show();

```

自定义View很简单,就是使用setView方法,传入一个自定义的View。在开发中,自定义View的使用频率还是很高的。例如:

```java

new Builder(this)

.setTitle("标题")

.setIconAttribute(android.R.attr.alertDialogIcon)

.setView(View.inflate(this, R.layout.custom_layout, null))

.show();

```

关于AlertDialog的销毁以及事件的监听,可以通过以下方式实现:

1. setCancelable(boolean cancelable):cancelable默认值是true,这时候点击弹框外部区域或者手机返回键,Dialog默认会销毁。反之,点击后Dialog不会销毁。如果在Dialog销毁时还想处理一些逻辑,就需要借助DialogInterface中的两个监听器:`onDismiss(DialogInterface dialog)` 和 `onCancel(DialogInterface dialog)`。例如:

```java

AlertDialog alertDialog = builder.create();

alertDialog.setOnDismissListener(new DialogInterface.OnDismissListener() {

@Override

public void onDismiss(DialogInterface dialog) {

// 这里处理对话框消失时的逻辑

}

});

alertDialog.setOnCancelListener(new DialogInterface.OnCancelListener() {

@Override

public void onCancel(DialogInterface dialog) {

// 这里处理对话框取消时的逻辑

}

});

alertDialog.show();

```

```java

public void cancel() { if (!mCanceled && mCancelMessage != null) {

mCanceled = true;

// Obtain a new message so this dialog can be re-used

Message.obtain(mCancelMessage).sendToTarget();

}

dismiss();

}

```

从这里可以看出,如果我们设置了setOnDismissListener,调用cancel和dismiss方法都会触发该监听器。但是如果设置setOnCancelListener,则只会在使用cancel方法时触发。差别就在这里。

当一个Dialog被销毁时,OnDismissListener将仍然被通知。但如果你希望在对话框被显示销毁(而不是正常解除)时被通知,则你应该使用setOnCancelListener()注册一个DialogInterface.OnCancelListener。

AlertDialog默认是不全屏的,如果想设置全屏可以使用如下方式:

```java

// 设置布局

View view = View.inflate(this, R.layout.layout_custom01, null);

view.setMinimumWidth(screenWidth);

// 创建AlertDialog

AlertDialog.Builder builder = new Builder(this)

.setTitle("title")

.setView(view);

// 获取AlertDialog实例

AlertDialog dialog = builder.create();

Window dialogWindow = dialog.getWindow();

// 设置屏幕底部居左对齐

dialogWindow.setGravity(Gravity.LEFT | Gravity.BOTTOM);

WindowManager.LayoutParams lp = dialogWindow.getAttributes();

lp.x = 0;

lp.y = 0;

dialogWindow.setAttributes(lp);

dialog.show();

```

以下是重构后的内容,保持了原有段落结构:

## AlertDialog简单使用基本介绍

AlertDialog的代码实现机制在以后的文章中将会有详细的解析。官方并不推荐开发者直接使用Dialog来创建弹出框,因此提供了AlertDialog。然而,从Android 3.0开始,官方又提供了一个新的类DialogFragment,宣称它拥有比AlertDialog更优的用户体验,因为它可以与Fragment的生命周期进行绑定。有关DialogFragment的使用,后续的文章中也会做简单的介绍。个人认为,在简单的实用场景下,既然AlertDialog已经能够满足开发需求,而并非特殊需求的情况下,我仍然选择使用AlertDialog。

在开发过程中,有时我们会遇到一些从底部向上滑动进入的弹框,比如购物车页面或者支付宝支付确认信息界面等。在这种情况下,如果想要自定义动画效果以提升用户体验,可以在AlertDialog中实现这一功能。不过,由于这种交互方式非常常见,Google官方针对此问题也提供了一个非常强大的控件:BottomSheetDialog。该控件最早是在support v23.2支持包中引入的。此外,还有与Fragment绑定的工具BottomSheetDialogFragment。总的来说,BottomSheetDialog可以说是弥补了SlidingDrawer、DrawerLayout、PopupWindow以及AlertDialog在底部弹框交互上的不足。在这里我只想说一个词:“shit”(糟糕)。为什么这些控件不能早一点出现呢?