可能有人要说不就是自定义dialog吗,网上一搜案例demo多得是,而且也不难,没什么好讲的。确实百度一下自定义的dialog数不胜数。但是大多数文章都是单一的布局实现单一的样式。假如说项目中有多个不同布局的dialog,比如三种、五种甚至十种,当然我只说假如。如果你的项目只有一种样式的对话框。那么也没有必要再看再往下看了。
如上所述,碰到多种样式的对话框应该怎么办呢?总不能项目需要几种就去写几种自定义样式吧?很显然我们不会那样做!所以这就是写这篇文章的用意。接下来我们将通过建造者模式来自定义dialog,并对自定义的dialog做下简单封装,最后实现的需求是只需要写dialog的布局文件即可实现任意样式的对话框!有木有很心动?闲话不多扯了,开撸!
国际惯例,开撸前先看效果图。图中使用同一个自定义dialog实现了两种不同样式的对话框。
一、用建造者模式实现自定义Dialog
1.首先我们要做的是去自定义Dialog,创建CustomDialog并继承Dialog。
2.明确Dialog所需要的属性,宽度、高度、Dialog的布局所对应的View、点击外部是否取消dialog、以及Dialog的主题(Theme),其中主题(Theme)我们没有定义在CustomDialog内部,而是定义到了Builder中。因此需要在CustomDialog内定义如下成员变量:
// dialog高度
private int height;
// dialog宽度
private int width;
// 点击外部是否可以取消
private boolean cancelTouchOutside;
// 对话框布局对应的View
private View dialogView;
3.接下来我们在CustomDialog内部创建静态内部类Builder,根据Dialog布局计算dialog宽高,并给定默认的对话框布局样式custom_dialog2。如下图(页面布局代码不再贴出):
Builder代码如下:
public static final class Builder {
private Context context;
private int height, width;
private boolean cancelTouchOutside;
private View mDialogView;
private int resStyle = -1;
public Builder(Context context) {
this.context = context;
mDialogView = LayoutInflater.from(context).inflate(R.layout.custom_dialog2, null);
// 计算dialog宽高
int measureSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
mDialogView.measure(measureSpec, measureSpec);
height = mDialogView.getMeasuredHeight();
width = mDialogView.getMeasuredWidth();
}
/**
* @param dialogView 关联dialog布局文件的View
* @return
*/
public Builder setDialogLayout(View dialogView) {
this.mDialogView = dialogView;
return this;
}
public Builder setHeightPx(int val) {
height = val;
return this;
}
public Builder setWidthPx(int val) {
width = val;
return this;
}
public Builder setHeightDp(int val) {
height = ScreenUtils.dp2px(context, val);
return this;
}
public Builder setWidthDp(int val) {
width = ScreenUtils.dp2px(context, val);
return this;
}
/**
* 设置主题
*
* @param resStyle
* @return
*/
public Builder setTheme(int resStyle) {
this.resStyle = resStyle;
return this;
}
/**
* 设置点击dialog外部是否取消dialog
*
* @param val
* @return
*/
public Builder cancelTouchOutside(boolean val) {
cancelTouchOutside = val;
return this;
}
/**
* 给dialog中的view添加点击事件
*
* @param viewResId 被点击view的id
* @param listener
* @return
*/
public Builder addViewOnclick(int viewResId, View.OnClickListener listener) {
mDialogView.findViewById(viewResId).setOnClickListener(listener);
return this;
}
/**
* 确定键监听
* @param confirm
* @param listener
* @return
*/
public Builder addConfirmClickListener(String confirm, View.OnClickListener listener) {
TextView tvConfirm = (TextView) mDialogView.findViewById(R.id.tv_confirm);
tvConfirm.setText(confirm);
tvConfirm.setOnClickListener(listener);
return this;
}
/**
* 取消键监听
* @param cancel
* @param listener
* @return
*/
public Builder addCancelClickListener(String cancel, View.OnClickListener listener) {
TextView tvCancel = (TextView) mDialogView.findViewById(R.id.tv_cancel);
tvCancel.setText(cancel);
tvCancel.setOnClickListener(listener);
return this;
}
/**
* 设置内容
* @param content
* @return
*/
public Builder setContent(String content) {
TextView tvTitle = (TextView) mDialogView.findViewById(R.id.tv_dialog_content);
tvTitle.setText(content);
return this;
}
/**
* 设置取消键颜色
* @param color 颜色
* @return
*/
public Builder setCancelColor(int color){
TextView tvCancel= (TextView) mDialogView.findViewById(R.id.tv_cancel);
tvCancel.setTextColor(color);
return this;
}
/**
* 设置确定键颜色
* @param color 颜色
* @return
*/
public Builder setConfirmColor(int color){
TextView tvCancel= (TextView) mDialogView.findViewById(R.id.tv_confirm);
tvCancel.setTextColor(color);
return this;
}
/**
* 显示一个按钮的弹窗
* @return
*/
public Builder showOneButton() {
mDialogView.findViewById(R.id.tv_cancel).setVisibility(View.GONE);
mDialogView.findViewById(R.id.view_dialog).setVisibility(View.GONE);
return this;
}
public CustomDialog build() {
if (resStyle != -1) {
return new CustomDialog(this, resStyle);
} else {
return new CustomDialog(this);
}
}
}
5.上面代码在build()方法中实例化了CustomDialog,因此我们不要忘了CustomDialog中的构造方法。在构造方法中获取Builder中的属性。构造方法有两个,如下:
private CustomDialog(Builder builder) {
super(builder.context);
height = builder.height;
width = builder.width;
cancelTouchOutside = builder.cancelTouchOutside;
dialogView = builder.mDialogView;
}
private CustomDialog(Builder builder, int theme) {
super(builder.context, theme);
height = builder.height;
width = builder.width;
cancelTouchOutside = builder.cancelTouchOutside;
dialogView = builder.mDialogView;
}
6.最后还要在onCreate()中为dialog设置布局和一些其他属性。代码如下:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(dialogView);
// 设置Touch的时候是否取消Dialog
setCanceledOnTouchOutside(cancelTouchOutside);
Window window = getWindow();
WindowManager.LayoutParams params = window.getAttributes();
params.gravity = Gravity.CENTER;
params.height = height;
params.width = width;
window.setAttributes(params);
}
这样自定义的Dialog就完成了。
二、对自定义的dialog简单封装。
完成自定义dialog之后接下来我们在项目的BaseActivity中对自定义的dialog做下封装。由于在构建DIalog的建造者时已经事先给定了默认布局,因此先对默认布局做下封装。
1.在BaseActivity中封装只有一个Button的对话框,并指定半透明背景样式,代码如下:
/**
* @param content 内容
* @param confirm 按钮文字
* @param confirmListener 按钮监听
*/
public void showOneButtonDialog(String content, String confirm, View.OnClickListener confirmListener) {
dialog = new CustomDialog.Builder(this)
.setTheme(R.style.CustomDialog1)
.setContent(content)
.addConfirmClickListener(confirm, confirmListener)
.showOneButton()
.build();
dialog.show();
}
2.封装两个按钮的dialog,指定半透明背景。代码如下:
/**
* @param content 内容
* @param confirm 确定键文字
* @param cancel 取消键文字
* @param confirmListener 确定键监听
* @param cancelListener 取消键监听
*/
public void showTwoButtonDialog(String content, String confirm, String cancel,
View.OnClickListener confirmListener,
View.OnClickListener cancelListener) {
dialog = new CustomDialog.Builder(this)
.setTheme(R.style.CustomDialog1)
.setContent(content)
.addConfirmClickListener(confirm, confirmListener)
.addCancelClickListener(cancel, cancelListener)
.build();
dialog.show();
}
3.封装两个按钮切能改变按钮字体颜色的对话框,背景指定全透明,代码如下:
/**
* @param content 内容
* @param confirm 确定键文字
* @param cancel 取消键文字
* @param confirmColor 确定键颜色
* @param cancelColor 取消键颜色
* @param confirmListener 确定键监听
* @param cancelListener 取消键监听
*/
public void showTwoButtonDialog(String content, String confirm, String cancel,
@ColorInt int confirmColor, @ColorInt int cancelColor,
View.OnClickListener confirmListener,
View.OnClickListener cancelListener) {
dialog = new CustomDialog.Builder(this)
.setTheme(R.style.CustomDialog2)
.setContent(content)
.setConfirmColor(confirmColor)
.setCancelColor(cancelColor)
.addConfirmClickListener(confirm, confirmListener)
.addCancelClickListener(cancel, cancelListener)
.build();
dialog.show();
}
4.封装自定义样式的对话框,需要自行定义对话框布局文件。代码如下:
/**
* create custom dialog
* 可以定制任意的dialog样式
*
* @param dialogLayoutRes dialog布局资源文件
* @param cancelTouchOutside 点击外部是否可以取消
* @return
*/
public View createCustomDialog(@LayoutRes int dialogLayoutRes, boolean cancelTouchOutside) {
dialogView = LayoutInflater.from(this).inflate(dialogLayoutRes, null);
// 计算dialog宽高
int measureSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
dialogView.measure(measureSpec, measureSpec);
int height = dialogView.getMeasuredHeight();
int width = dialogView.getMeasuredWidth();
dialog = new CustomDialog.Builder(this)
.setTheme(R.style.CustomDialog1)
.setHeightPx(height)
.setWidthPx(width)
.cancelTouchOutside(cancelTouchOutside)
.setDialogLayout(dialogView).build();
dialog.show();
return dialogView;
}
自定义样式的dialog由于没有给定点击事件,因此需要在Activity中添加dismiss dialog的方法,可以在子Activity中自定义点击事件的时候调用,如下:
/**
* 隐藏dialog
*/
public void dismissDialog() {
if (dialog != null && dialog.isShowing()) {
dialog.dismiss();
}
}
三、在子Activity中根据需求显示不同的对话框。
MainActivity继承BaseActivity,并在MainActivity中调用BaseActivity中的不同对话框的方法显示对话框。代码如下:
// 显示一个按钮的对话框
private void showOneButton(){
showOneButtonDialog("一个Button的Dialog", "确定", new View.OnClickListener() {
@Override
public void onClick(View v) {
dismissDialog();
}
});
}
// 显示两个按钮的对话框
private void showTwoButton(){
showTwoButtonDialog("两个Button的Dialog", "确定", "取消",
new View.OnClickListener() {
@Override
public void onClick(View v) {
dismissDialog();
}
}, new View.OnClickListener() {
@Override
public void onClick(View v) {
dismissDialog();
}
});
}
// 显示两个按钮且可以改变按钮颜色且背景全透明的对话框
public void showTowButtonWithColor() {
showTwoButtonDialog("改变Button颜色的Dialog\n背景全透明", "确定", "取消",
Color.parseColor("#ff0000"), Color.parseColor("#00ff00"),
new View.OnClickListener() {
@Override
public void onClick(View v) {
dismissDialog();
}
}, new View.OnClickListener() {
@Override
public void onClick(View v) {
dismissDialog();
}
});
}
// 显示自定义样式的对话框
private void showCustomDialog() {
mDialogView1 = createCustomDialog(R.layout.custom_dialog1, false);
// 为自定义的dialog设置内容、添加点击事件
TextView viewById1 = (TextView) mDialogView1.findViewById(R.id.tv_dialog_content);
viewById1.setText("自定义样式的Dialog");
mDialogView1.findViewById(R.id.btn_dialog).setOnClickListener(this);
}
上面代码自定义对话框createCustomDialog()方法的返回值是自定义布局文件对应的View,因此可以通过该View获取到布局中的所有子View,然后为其设置内容或者监听事件。