美文网首页android 集结号精选案例Android开发学习
项目需求讨论-Android 自定义Dialog实现步骤及封装

项目需求讨论-Android 自定义Dialog实现步骤及封装

作者: 青蛙要fly | 来源:发表于2017-01-22 14:47 被阅读14667次

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

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

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

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

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

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

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

    public class ErrorDialog extends Dialog{
        public ErrorDialog(Context context) {
            super(context);
        }
    
        public ErrorDialog(Context context, int themeResId) {
            super(context, themeResId);
        }
    
        protected ErrorDialog(Context context, boolean cancelable
            , OnCancelListener cancelListener) {
            super(context, cancelable, cancelListener);
        }
    }
    

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

    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:

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="200dp"
        android:layout_height="250dp"
        android:background="@drawable/background_loginerror"
        android:gravity="center_horizontal"
        android:orientation="vertical">
    
        <ImageView
            android:layout_marginTop="30dp"
            android:layout_width="80dp"
            android:layout_height="80dp"
            android:src="@drawable/gong"
            />
    
        <TextView
            android:layout_marginTop="20dp"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="您输入的内容有误"
            />
    
        <Button
            android:id="@+id/btn_cancel"
            android:layout_marginTop="20dp"
            android:layout_width="120dp"
            android:layout_height="40dp"
            android:text="@string/confirm"
            android:background="@drawable/background_loginbtn"
            />
    </LinearLayout>
    

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

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

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

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

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

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

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

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

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

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

    <style name="Dialog" parent="android:style/Theme.Dialog">
        <item name="android:background">@android:color/transparent</item>
        <item name="android:windowBackground">@android:color/transparent</item>
        <item name="android:windowNoTitle">true</item>
    </style>
    

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

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

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

    然后我们看到效果了:



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

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

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

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

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

     android:layout_width="200dp"
     android:layout_height="250dp"
    

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


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

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

    最终的自定义ErrorDialog代码:

    public class ErrorDialog extends Dialog {
        public Context context;
    
        public ErrorDialog(Context context) {
            super(context);
            this.context = context;
        }
    
        public ErrorDialog(Context context, int theme) {
            super(context, theme);
            this.context = context;
        }
    
        public ErrorDialog(Context context, boolean cancelable, OnCancelListener cancelListener) {
            super(context, cancelable, cancelListener);
            this.context = context;
        }
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            View view = View.inflate(context, R.layout.dialog_loginerror, null);
            setContentView(view);
    
            setCanceledOnTouchOutside(false);
    
            Window win = getWindow();
            WindowManager.LayoutParams lp = win.getAttributes();
            lp.height = DensityUtil.dip2px(context, 250);
            lp.width = DensityUtil.dip2px(context, 200);
            win.setAttributes(lp);
    
            view.findViewById(R.id.btn_cancel).setOnClickListener(new Button.OnClickListener() {
                @Override
                public void onClick(View view) {
                    dismiss();
                }
            });
        }
    }
    
    

    最后生成的界面如下:


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

    上面只是介绍了自定义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模式及自动化生成代码插件

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

    public class CustomDialog extends Dialog {
        private Context context;
        private int height, width;
        private boolean cancelTouchout;
        private View view;
    
        private CustomDialog(Builder builder) {
            super(builder.context);
            context = builder.context;
            height = builder.height;
            width = builder.width;
            cancelTouchout = builder.cancelTouchout;
            view = builder.view;
        }
    
    
        private CustomDialog(Builder builder, int resStyle) {
            super(builder.context, resStyle);
            context = builder.context;
            height = builder.height;
            width = builder.width;
            cancelTouchout = builder.cancelTouchout;
            view = builder.view;
        }
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
    
            setContentView(view);
    
            setCanceledOnTouchOutside(cancelTouchout);
    
            Window win = getWindow();
            WindowManager.LayoutParams lp = win.getAttributes();
            lp.gravity = Gravity.CENTER;
            lp.height = height;
            lp.width = width;
            win.setAttributes(lp);
        }
    
        public static final class Builder {
    
            private Context context;
            private int height, width;
            private boolean cancelTouchout;
            private View view;
            private int resStyle = -1;
    
    
            public Builder(Context context) {
                this.context = context;
            }
    
            public Builder view(int resView) {
                view = LayoutInflater.from(context).inflate(resView, null);
                return this;
            }
    
            public Builder heightpx(int val) {
                height = val;
                return this;
            }
    
            public Builder widthpx(int val) {
                width = val;
                return this;
            }
    
            public Builder heightdp(int val) {
                height = DensityUtil.dip2px(context, val);
                return this;
            }
    
            public Builder widthdp(int val) {
                width = DensityUtil.dip2px(context, val);
                return this;
            }
    
            public Builder heightDimenRes(int dimenRes) {
                height = context.getResources().getDimensionPixelOffset(dimenRes);
                return this;
            }
    
            public Builder widthDimenRes(int dimenRes) {
                width = context.getResources().getDimensionPixelOffset(dimenRes);
                return this;
            }
    
            public Builder style(int resStyle) {
                this.resStyle = resStyle;
                return this;
            }
    
            public Builder cancelTouchout(boolean val) {
                cancelTouchout = val;
                return this;
            }
    
            public Builder addViewOnclick(int viewRes,View.OnClickListener listener){
                view.findViewById(viewRes).setOnClickListener(listener);
                return this;
            }
    
    
            public CustomDialog build() {
                if (resStyle != -1) {
                    return new CustomDialog(this, resStyle);
                } else {
                    return new CustomDialog(this);
                }
            }
        }
    }
    
    
    

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

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

    CustomDialog.Builder builder = new CustomDialog.Builder(this);
    dialog =
        builder.cancelTouchout(false)
                .view(R.layout.dialog_loginerror)
                .heightDimenRes(R.dimen.dialog_loginerror_height)
                .widthDimenRes(R.dimen.dialog_loginerror_width)
                .style(R.style.Dialog)
                .addViewOnclick(R.id.btn_cancel,new View.OnClickListener() {
                    @Override
                    public void onClick(View view) {
                        dialog.dismiss();
                    }
                })
                .build();
    dialog.show();
    

    真是简单!!!!!

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

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

    View.OnClickListener listener = new View.OnClickListener() {
        @Override
        public void onClick(View view) {
            switch (view.getId()){
                case R.id.jbperson :
                    Toast.makeText(aty, "选择经办人按钮", Toast.LENGTH_SHORT).show();
                    break;
                case R.id.spperson:
                    Toast.makeText(aty, "选择审批人按钮", Toast.LENGTH_SHORT).show();
                    break;
                case R.id.confirmbtn:
                    Toast.makeText(aty, "点击确定按钮", Toast.LENGTH_SHORT).show();
                     break;
            }
        }
    };
    
    CustomDialog.Builder builder = new CustomDialog.Builder(this);
    CustomDialog dialog = builder
            .style(R.style.Dialog)
            .heightDimenRes(R.dimen.dilog_identitychange_height)
            .widthDimenRes(R.dimen.dilog_identitychange_width)
            .cancelTouchout(false)
            .view(R.layout.dialog_identitychange)
            .addViewOnclick(R.id.jbperson,listener)
            .addViewOnclick(R.id.spperson,listener)
            .addViewOnclick(R.id.confirmbtn,listener)
            .build();
            
    dialog.show();
    

    我们用手机查看效果:

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

    相关文章

      网友评论

      • developerzjy:一个dialog其实就是一个界面,我觉得dialog.setContentView()足够了,继承dialog反而麻烦而且没那么灵活了
      • d40c4a1300d2:点击里面按钮跳转另一个界面怎么写?
      • April7zn:Builder:
        public CustomDialog build() {
        if(dialog == null) {
        if (resStyle != -1) {
        dialog = new CustomDialog(this, resStyle);
        } else {
        dialog = new CustomDialog(this);
        }
        }
        return dialog;
        }

        Dialog:
        public static void dismissDialog(){
        dialog.dismiss();
        dialog = null;
        }
        public void show(){
        if(!dialog.isShowing()) {
        super.show();
        }
        }
        April7zn:解决dismiss未初始化, 而且禁止弹出多个
      • 拽是男人的本性:点击取消的那个监听里面 dialog 提示没有实例化 没法调用dissmiss() 怎么解决
      • 我一定会学会:问一个棘手的问题,你这个如果代码继承CustomDialog然后写一个封装好的类,不然,每次都得去代码中重复生成一模一样的dialog!
      • 好kan故事:大神,中间的内容怎么修改啊
      • 薅韭菜:DensityUtil 这个类发一下
      • Ggx的代码之旅:说话方式很好玩,xxxxxxx...倒不是说不行,xxxxxxxxx /斜眼笑
      • dd61ef7c40ca:循序渐进,大赞!
      • o安好岁月o:正是我在找的,感谢楼主!好人一生平安!
        青蛙要fly:@o安好岁月o 感谢支持:blush: 不过后面一般弹框推荐用DialogFragment,谷歌也是推荐这个。我现在也都一般使用这个。
      • 890cb3078f40:在switch那里,case后应该return出去,不然会按顺序将所有case都运行一遍
        程序猿在广东:@青蛙要fly 用break没有错啊,没有case到对应id 程序不会进去的
        青蛙要fly:@正在从小白到大白的JY 是的。。哈哈。。。漏了。
      • Nina老师:遇到点击dialog控件关闭dialog怎么实现呢
        拽是男人的本性:@YuAntony 点击取消的那个监听里面 dialog 提示没有实例化 没法调用dissmiss() 这个问题解决了吗?
        YuAntony:@SmartSean 点击取消的那个监听里面 dialog 提示没有实例化 没法调用dissmiss()
        SmartSean:直接拿dialog,调用dialog.dissmiss();
      • 追梦者king:楼主你这个customdialog是用那个innerbuilder生成的?我用你的属性,怎么生成的比你少呢,也没onCreate方法
      • Luke_单车:问下,dialog去掉标题了,感觉不居中,怎么解决呢
      • Alien的小窝:个人认为PopWindow比较难用,Bugs也多,Dialog需要设置各种属性,看了几个比较流行的开源项目,直接用 getDecorview().addView(子View);的方式更好一些,简单实用。
      • 079c5789cfdf:你好可以改变dialog在屏幕上面的显示位置吗,比如在屏幕的bottom显示
        SmartSean:那你直接使用popupwindow好了
      • f0cf0e17c2c6:其实我想问镇楼图出自哪里,还不错的样子
        青蛙要fly:@lixiaoli 爱壁纸应用里面找到的
      • 伟大的小炮殿下:......弱弱的问下为什么不用pop
        青蛙要fly:@伟大的小炮殿下 比如弹框跳出来,底下的界面会被遮上一层灰色的颜色。而直接继承Dialog的话。这种就不用再自己弄了。倒不是说pop不能实现。
      • 年才下:感谢还在辛苦写博客的作者
      • 79145991b60b:好腻害啊!
      • 浪浪的麦子:正是我项目想要的

      本文标题:项目需求讨论-Android 自定义Dialog实现步骤及封装

      本文链接:https://www.haomeiwen.com/subject/filgbttx.html