Android 自定义Dialog实现步骤及封装

在项目中,我们会遇到各种各样的界面需求,比如对话框和选择框,都是会配合具体项目的UI界面来做,而不是说用自带的弹出框。比如下面在登录界面的二个对话框效果。都是我在做具体项目中所要求实现的:

1.输入有误时弹出的对话框

%title插图%num

2.选择角色登录时的对话框

%title插图%num

这里倒不是说自定义Dialog的教程,因为自定义Dialog大家基本都会。只是我在登录界面写了这二个Dialog之后,我就觉得好烦,然后决定封装了一个类,因为后面不同界面还有很多不同的弹框。为后期节省时间。倒不是说我这个封装类写的有多好,只是写出来,大家可以看下,然后哪里不好可以跟我提下意见。

让我们一步步来看是如何自定这个自定义对话框及如何来进行封装自己的自定义Dialog工具类。我就按照实际项目中,我的开发步骤来说明。

如何生成这种自定义对话框

实际开发中,我看到了*个效果图中的对话框,于是我马上大手一挥,自定义了一个类ErrorDialog,继承了Dialog。

  1. public class ErrorDialog extends Dialog{
  2. public ErrorDialog(Context context) {
  3. super(context);
  4. }
  5. public ErrorDialog(Context context, int themeResId) {
  6. super(context, themeResId);
  7. }
  8. protected ErrorDialog(Context context, boolean cancelable
  9. , OnCancelListener cancelListener) {
  10. super(context, cancelable, cancelListener);
  11. }
  12. }

我们可以看到有三个构造函数,这三个构造函数不是一定要都实现,但至少要实现一个构造函数。我们来具体说明下各个构造函数的作用。

  1. ErrorDialog(Context context):
    单纯传入Context,用的比较多。在代码中通过new ErrorDialog(context);来获得Dialog的实例,然后使用show()方法进行展现。
  2. ErrorDialog(Context context, int themeResId)
    大家可以看到比*个构造函数多了一个themeResId,就是我们可以传入一个主题值,比如R.style.XXX。然后构造函数中会调用super(context, themeResId);等会生成的Dialog就会带有这个R.style.XXX所设置的效果。
  3. ErrorDialog(Context context, boolean cancelable, OnCancelListener cancelListener):
    大家都知道,对话框弹出来后,默认情况下,我们在屏幕上触摸对话框以外的屏幕的界面,对话框会默认消失。我们平时做对话框的时候一般都是让这个对话框点击外面的其他界面地方的时候不让对话框消失,我们一般在代码中会这么写:setCanceledOnTouchOutside(false);。为什么我提这个,没错,这个构造函数里面的那个boolean cancelable控制的就是这个功能,<1>当传入为true的时候,就是可以点击外面来让对话框消失,然后消失的时候会调用后面第三个参数的cancelListener这个listener里面的方法。我们可以在里面做相应的监听事件。<2>当传入false。那么点击外面区域,这个对话框也就不会消失,而且后面的那个listener也不会被调用。

好了,构造函数说好后。我们来具体看如何生成界面Dialog界面。于是我大手再次一挥。写了个对话框所需要的效果的Layout:

  1. <?xml version=”1.0″ encoding=”utf-8″?>
  2. <LinearLayout xmlns:android=“http://schemas.android.com/apk/res/android”
  3. android:layout_width=“200dp”
  4. android:layout_height=“250dp”
  5. android:background=“@drawable/background_loginerror”
  6. android:gravity=“center_horizontal”
  7. android:orientation=“vertical”>
  8. <ImageView
  9. android:layout_marginTop=“30dp”
  10. android:layout_width=“80dp”
  11. android:layout_height=“80dp”
  12. android:src=“@drawable/gong”
  13. />
  14. <TextView
  15. android:layout_marginTop=“20dp”
  16. android:layout_width=“wrap_content”
  17. android:layout_height=“wrap_content”
  18. android:text=“您输入的内容有误”
  19. />
  20. <Button
  21. android:id=“@+id/btn_cancel”
  22. android:layout_marginTop=“20dp”
  23. android:layout_width=“120dp”
  24. android:layout_height=“40dp”
  25. android:text=“@string/confirm”
  26. android:background=“@drawable/background_loginbtn”
  27. />
  28. </LinearLayout>

效果如下图所示(那个感叹号的图片我这边因为切图没有的问题。临时换成了工商的图标。反正不影响我们开发教程):

%title插图%num

然后我们在自定义的ErrorDialog中写oncreate方法:

  1. @Override
  2. protected void onCreate(Bundle savedInstanceState) {
  3. super.onCreate(savedInstanceState);
  4. View view = View.inflate(context,R.layout.dialog_loginerror,null);
  5. setContentView(view);
  6. }

然后这时候我以为就跟继承Activity一样。变成了我自定义的布局界面。然后我满心欢喜的在Activity中调用了:
ErrorDialog dialog = new ErrorDialog(this); dialog.show();

%title插图%num

WTF!!
这是逗我吗,我的自定义布局明明是个圆角啊。怎么变成了个长方形。
所以我就把我们自定义布局的背景色换成其黑色。看下效果:

%title插图%num

这下首先知道了。我们其实自定义的layout类似于是盖在了底部白色的背景上面,恰好我们的自定义布局也是白色。所以我们现在首先要把底部的那个白色背景变为透明,那样,就会出现我们自定义布局的圆角了

那我们下一步的目的就是要设置Dialog自定义的theme。

把Dialog自带的白色背景色改为透明即可,很简单。百度一搜一大把。哈哈。其实说到底就是继承android:style/Theme.Dialog主题,然后再覆写其中的几个相关属性,比如背景设置为透明,去除自带的title等属性。

(我在网上看的时候,网上有人推荐Android4.0后,不要使用Theme.Dialog。改为使用Theme.Holo.DialogWhenLarge。
Android4: 请放弃使用Theme.Dialog
当然对我们这个自定义布局需求,继承哪个都能实现效果。就看大家怎么选择了。
)

好,那我们就自定义继承Theme.Dialog:

  1. <style name=“Dialog” parent=“android:style/Theme.Dialog”>
  2. <item name=“android:background”>@android:color/transparent</item>
  3. <item name=“android:windowBackground”>@android:color/transparent</item>
  4. <item name=“android:windowNoTitle”>true</item>
  5. </style>

(如果有其他需求,再覆写其他的属性即可,本例中就只需要改这三个)

好了。然后在上面我们介绍过的生成Dialog实例中的第二个构造函数,是传入Theme。我们调用:

  1. ErrorDialog dialog = new ErrorDialog(this,R.style.Dialog);
  2. dialog.show();

然后我们看到效果了:

 

%title插图%num

 

起码形状变成我们的自定义布局的形状了。哈哈。但是这个Dialog大小和我们的自定义布局大小不同。

下一步要处理Dialog呈现的自定义布局的大小

还是老样子,百度一搜一大把,好吧。我实在是太懒了。(不服气我懒的话,过来打我哈。O(∩_∩)O)

  1. Window win = getWindow();
  2. WindowManager.LayoutParams lp = win.getAttributes();
  3. lp.gravity = Gravity.CENTER;
  4. lp.height = DensityUtil.dip2px(context,250);
  5. lp.width = DensityUtil.dip2px(context,200);
  6. win.setAttributes(lp);

我来解释下,因为上面我们自定义布局的大小就是

  1. android:layout_width=“200dp”
  2. android:layout_height=“250dp”

所以我们这里也设置这个对话框的大小也设置为相同大小,这样就等于显示出我们自定义布局大小。这里因为高和宽改为我们自己自定义布局大小,所以lp.gravity = Gravity.CENTER;这句也可以不写,因为反正是正好完全填充。


所以我们当前的自定义Dialog代码变为:

  1. @Override
  2. protected void onCreate(Bundle savedInstanceState) {
  3. super.onCreate(savedInstanceState);
  4. View view = View.inflate(context,R.layout.dialog_loginerror,null);
  5. setContentView(view);
  6. Window win = getWindow();
  7. WindowManager.LayoutParams lp = win.getAttributes();
  8. lp.height = DensityUtil.dip2px(context,250);
  9. lp.width = DensityUtil.dip2px(context,200);
  10. win.setAttributes(lp);
  11. }
点击事件
  1. 一般我们项目中跳出了对话框,点击对话框外面的区域,是不能默认让对话框消失的。所以我们需要添加setCanceledOnTouchOutside(false);
  2. 自定义布局上面的按钮点击事件的添加很简单,因为上面已经拿到了自定义布局的view的对象。比如我们上面的自定义布局有个<确定>按钮,我们点击按钮让对话框消失。我们只需要:
  1. view.findViewById(R.id.btn_cancel).setOnClickListener(new Button.OnClickListener() {
  2. @Override
  3. public void onClick(View view) {
  4. dismiss();
  5. }
  6. });

*终的自定义ErrorDialog代码:

  1. public class ErrorDialog extends Dialog {
  2. public Context context;
  3. public ErrorDialog(Context context) {
  4. super(context);
  5. this.context = context;
  6. }
  7. public ErrorDialog(Context context, int theme) {
  8. super(context, theme);
  9. this.context = context;
  10. }
  11. public ErrorDialog(Context context, boolean cancelable, OnCancelListener cancelListener) {
  12. super(context, cancelable, cancelListener);
  13. this.context = context;
  14. }
  15. @Override
  16. protected void onCreate(Bundle savedInstanceState) {
  17. super.onCreate(savedInstanceState);
  18. View view = View.inflate(context, R.layout.dialog_loginerror, null);
  19. setContentView(view);
  20. setCanceledOnTouchOutside(false);
  21. Window win = getWindow();
  22. WindowManager.LayoutParams lp = win.getAttributes();
  23. lp.height = DensityUtil.dip2px(context, 250);
  24. lp.width = DensityUtil.dip2px(context, 200);
  25. win.setAttributes(lp);
  26. view.findViewById(R.id.btn_cancel).setOnClickListener(new Button.OnClickListener() {
  27. @Override
  28. public void onClick(View view) {
  29. dismiss();
  30. }
  31. });
  32. }
  33. }

*后生成的界面如下:

%title插图%num

—————————————-本文正文,封装君正式登场—————————————————–

上面只是介绍了自定义Dialog的基本知识。而且这个ErrorDialog只能用于*个效果图所需的Dialog需求,然后比如我们要第二个效果图的需求。然后自己再写一个ChangeDialog???一个项目有5种不同的界面Dialog。我要写5个自定义Dialog类???答案当然是NO,NO,NO。

我们来看下,上面我们完成ErrorDialog的时候,到底需要哪些东西,才能*后完成一个自定义Dialog。

  1. Context
  2. R.style.XXXX
  3. R.layout.XXXX
  4. setCanceledOnTouchOutside(XXXX);是否允许点击外部区域来让Dialog消失
  5. WindowManager.LayoutParams对象类的height和width。
  6. 自定义布局上各个View的点击事件

基本是上述五个需求。(额外需求,大家就在这基础上封装好的类中添加自己的需求即可)

我们也是模仿Dialog建立的Builder模式,自己写个封装类。
(Builder模式的介绍和用Android Studio插件来快速自动生成代码,大家可以来看下我已经写得文章:经典Builder/变种Builder模式及自动化生成代码插件)

我先上代码再来进行查看:

  1. public class CustomDialog extends Dialog {
  2. private Context context;
  3. private int height, width;
  4. private boolean cancelTouchout;
  5. private View view;
  6. private CustomDialog(Builder builder) {
  7. super(builder.context);
  8. context = builder.context;
  9. height = builder.height;
  10. width = builder.width;
  11. cancelTouchout = builder.cancelTouchout;
  12. view = builder.view;
  13. }
  14. private CustomDialog(Builder builder, int resStyle) {
  15. super(builder.context, resStyle);
  16. context = builder.context;
  17. height = builder.height;
  18. width = builder.width;
  19. cancelTouchout = builder.cancelTouchout;
  20. view = builder.view;
  21. }
  22. @Override
  23. protected void onCreate(Bundle savedInstanceState) {
  24. super.onCreate(savedInstanceState);
  25. setContentView(view);
  26. setCanceledOnTouchOutside(cancelTouchout);
  27. Window win = getWindow();
  28. WindowManager.LayoutParams lp = win.getAttributes();
  29. lp.gravity = Gravity.CENTER;
  30. lp.height = height;
  31. lp.width = width;
  32. win.setAttributes(lp);
  33. }
  34. public static final class Builder {
  35. private Context context;
  36. private int height, width;
  37. private boolean cancelTouchout;
  38. private View view;
  39. private int resStyle = –1;
  40. public Builder(Context context) {
  41. this.context = context;
  42. }
  43. public Builder view(int resView) {
  44. view = LayoutInflater.from(context).inflate(resView, null);
  45. return this;
  46. }
  47. public Builder heightpx(int val) {
  48. height = val;
  49. return this;
  50. }
  51. public Builder widthpx(int val) {
  52. width = val;
  53. return this;
  54. }
  55. public Builder heightdp(int val) {
  56. height = DensityUtil.dip2px(context, val);
  57. return this;
  58. }
  59. public Builder widthdp(int val) {
  60. width = DensityUtil.dip2px(context, val);
  61. return this;
  62. }
  63. public Builder heightDimenRes(int dimenRes) {
  64. height = context.getResources().getDimensionPixelOffset(dimenRes);
  65. return this;
  66. }
  67. public Builder widthDimenRes(int dimenRes) {
  68. width = context.getResources().getDimensionPixelOffset(dimenRes);
  69. return this;
  70. }
  71. public Builder style(int resStyle) {
  72. this.resStyle = resStyle;
  73. return this;
  74. }
  75. public Builder cancelTouchout(boolean val) {
  76. cancelTouchout = val;
  77. return this;
  78. }
  79. public Builder addViewOnclick(int viewRes,View.OnClickListener listener){
  80. view.findViewById(viewRes).setOnClickListener(listener);
  81. return this;
  82. }
  83. public CustomDialog build() {
  84. if (resStyle != –1) {
  85. return new CustomDialog(this, resStyle);
  86. } else {
  87. return new CustomDialog(this);
  88. }
  89. }
  90. }
  91. }

我这边的Builder中对height和width写了三种方式,比如直接写入px的值就调用heightpx(),如果直接写入dp值,就调用heightdp()。不过*多的应该还是调用heightDimenRes()方法。因为一般我们在写自定义layout布局的时候,height和width的数值肯定是去dimen.xml中获取。所以我们在代码中生成这个自定义对话框的时候,也就直接调用了heightDimenRes(R.dimen.XXX)。这样。我们什么时候需求变了,说这个对话框的大小要进行更改,我们不需要更改代码,只需要在demen.xml中将数值修改即可。

然后我们再来写上面的ErrorDialog:

  1. CustomDialog.Builder builder = new CustomDialog.Builder(this);
  2. dialog =
  3. builder.cancelTouchout(false)
  4. .view(R.layout.dialog_loginerror)
  5. .heightDimenRes(R.dimen.dialog_loginerror_height)
  6. .widthDimenRes(R.dimen.dialog_loginerror_width)
  7. .style(R.style.Dialog)
  8. .addViewOnclick(R.id.btn_cancel,new View.OnClickListener() {
  9. @Override
  10. public void onClick(View view) {
  11. dialog.dismiss();
  12. }
  13. })
  14. .build();
  15. dialog.show();

真是简单!!!!!

好了我们现在要第二个效果图的对话框了。比如我现在为了简单。就做了个简单的自定义Layout。

%title插图%num

然后点击“经办人”,“审批人”,“确认”按钮,有不同点击效果。
生成这个对话框代码如下:

  1. View.OnClickListener listener = new View.OnClickListener() {
  2. @Override
  3. public void onClick(View view) {
  4. switch (view.getId()){
  5. case R.id.jbperson :
  6. Toast.makeText(aty, “选择经办人按钮”, Toast.LENGTH_SHORT).show();
  7. break;
  8. case R.id.spperson:
  9. Toast.makeText(aty, “选择审批人按钮”, Toast.LENGTH_SHORT).show();
  10. break;
  11. case R.id.confirmbtn:
  12. Toast.makeText(aty, “点击确定按钮”, Toast.LENGTH_SHORT).show();
  13. break;
  14. }
  15. }
  16. };
  17. CustomDialog.Builder builder = new CustomDialog.Builder(this);
  18. CustomDialog dialog = builder
  19. .style(R.style.Dialog)
  20. .heightDimenRes(R.dimen.dilog_identitychange_height)
  21. .widthDimenRes(R.dimen.dilog_identitychange_width)
  22. .cancelTouchout(false)
  23. .view(R.layout.dialog_identitychange)
  24. .addViewOnclick(R.id.jbperson,listener)
  25. .addViewOnclick(R.id.spperson,listener)
  26. .addViewOnclick(R.id.confirmbtn,listener)
  27. .build();
  28. dialog.show();

 

真是简单!!!!!!!哈哈。当然我只是做了简单的封装。大家可以提出不同的意见。