美文网首页AndroidAndroid开发Android资源收录
如何从0构造出一个万能的Dialog

如何从0构造出一个万能的Dialog

作者: 码农大表哥 | 来源:发表于2017-06-02 01:15 被阅读513次

    前言

    • 第一次在简书发表文章,突然就有点小紧张,写不好,会不会有人打我啊。
    • 该片文章所讲的Dialog适用于那种漂亮可爱妹纸设计的多种弹框,各种弹,各种动画,各种方位、姿势和变化。
    • 写该文章的初衷在于刚好我们公司就有这么一对可爱的设计师,这样多样式的弹框,如果一个个单独写会造成代码臃肿和可读性十分差,所以做了一下二次封装。
    • 本文章其实早在两个月前就打算写了,由于项目时间十分紧张(通宵加班加得我怀疑猿生),所以拖到了现在,终于开始写了(我在调休,嘿嘿嘿...)。

    本文会用到Builder设计模式:Android开发---Builder 模式必知必会

    • 给大家献上效果图,弹框自行yy出来的,可能有点丑,不过这不是重点,因为出图是设计妹纸的事情。
    效果一.png 效果二.png 效果三.png 效果四.png 效果五.png 效果六.png

    以上是部分效果展示,下面我们将进入正题:

    目录

    1.继承Dialog,高仿一个AlertDialog

    1.1 开始按照v7包的AlertDialog撸一个轮子
    • 阅读并理解AlertDialog源码

    上一个链接让大家看看如何简单自定义:Android AlertDialog/AlertDialog.builder 以及 自定义AlertDialog方法

    • 在package android.support.v7.app下面我们找到这个类AlertDialog.class 我精简了一些 剩下的主要内容如下:
    private final AlertController mAlert;
    static final int LAYOUT_HINT_NONE = 0;
    static final int LAYOUT_HINT_SIDE = 1;
    
    protected AlertDialog(@NonNull Context context) {
        this(context, 0);
    }
    
    
    protected AlertDialog(@NonNull Context context, @StyleRes int themeResId) {
        super(context, resolveDialogTheme(context, themeResId));
        mAlert = new AlertController(getContext(), this, getWindow());
    }
    
    protected AlertDialog(@NonNull Context context, boolean cancelable,
            @Nullable OnCancelListener cancelListener) {
        this(context, 0);
        setCancelable(cancelable);
        setOnCancelListener(cancelListener);
    }
    
    private static int resolveDialogTheme(@NonNull Context context, @StyleRes int resid) {
        if (resid >= 0x01000000) {   // start of real resource IDs.
            return resid;
        } else {
            TypedValue outValue = new TypedValue();
            context.getTheme().resolveAttribute(R.attr.alertDialogTheme, outValue, true);
            return outValue.resourceId;
        }
    }
    
    
    public void setView(View view) {
        mAlert.setView(view);
    }
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mAlert.installContent();
    }
    
    
    public static class Builder {
        private final AlertController.AlertParams P;
        private final int mTheme;
    
      
        public Builder(@NonNull Context context) {
            this(context, resolveDialogTheme(context, 0));
        }
    
        public Builder(@NonNull Context context, @StyleRes int themeResId) {
            P = new AlertController.AlertParams(new ContextThemeWrapper(
                    context, resolveDialogTheme(context, themeResId)));
            mTheme = themeResId;
        }
    
      
        @NonNull
        public Context getContext() {
            return P.mContext;
        }
    
    
        public Builder setCancelable(boolean cancelable) {
            P.mCancelable = cancelable;
            return this;
        }
    
      
        public Builder setOnCancelListener(OnCancelListener onCancelListener) {
            P.mOnCancelListener = onCancelListener;
            return this;
        }
    
    
        public Builder setOnDismissListener(OnDismissListener onDismissListener) {
            P.mOnDismissListener = onDismissListener;
            return this;
        }
    
      
        public Builder setOnKeyListener(OnKeyListener onKeyListener) {
            P.mOnKeyListener = onKeyListener;
            return this;
        }
    
       
        public Builder setView(int layoutResId) {
            P.mView = null;
            P.mViewLayoutResId = layoutResId;
            P.mViewSpacingSpecified = false;
            return this;
        }
    
       
        public Builder setView(View view) {
            P.mView = view;
            P.mViewLayoutResId = 0;
            P.mViewSpacingSpecified = false;
            return this;
        }
    
        @Deprecated
        public Builder setView(View view, int viewSpacingLeft, int viewSpacingTop,
                int viewSpacingRight, int viewSpacingBottom) {
            P.mView = view;
            P.mViewLayoutResId = 0;
            P.mViewSpacingSpecified = true;
            P.mViewSpacingLeft = viewSpacingLeft;
            P.mViewSpacingTop = viewSpacingTop;
            P.mViewSpacingRight = viewSpacingRight;
            P.mViewSpacingBottom = viewSpacingBottom;
            return this;
        }
    
        public AlertDialog create() {
            final AlertDialog dialog = new AlertDialog(P.mContext, mTheme);
            P.apply(dialog.mAlert);
            dialog.setCancelable(P.mCancelable);
            if (P.mCancelable) {
                dialog.setCanceledOnTouchOutside(true);
            }
            dialog.setOnCancelListener(P.mOnCancelListener);
            dialog.setOnDismissListener(P.mOnDismissListener);
            if (P.mOnKeyListener != null) {
                dialog.setOnKeyListener(P.mOnKeyListener);
            }
            return dialog;
        }
    
        public AlertDialog show() {
            final AlertDialog dialog = create();
            dialog.show();
            return dialog;
        }
    }
    

    从源码可以看出,类中有个AlertController类型的属性,我们进入这个类文件可以看到定义了很多属性,比如Window View 上下左右边距等等,这个类的作用我们将会在2.1中提到。

    • 开始自定义PowerfulDialog

    我们新建一个PowerfulDialog仿照这个类,去继承Dialog,这里有人拿着刀就要说了,人家是继承的AppCompatDialog(这里我解释一下AppCompatDialog ,点进去看源码,发现它还是继承的Dialog,是为了简便的实现Material Design风格的弹框),所以很多人使用AlertDialog,基本都会是长这样:

    md风格弹框.png

    当然了,原生的AlertDialog可以通过Biulder中的setView方法去设置自定义的View但是,这样写达不到我们理想中的代码量尽可能少的要求,好了,现在我们直接继承Dialog,不熟悉Builder的建议可以直接copy一份源码,直接在源码上面修改。

    我们会在PowerFulDialog中写一个Builder的静态内部类,这个静态类主要是用来通知控制器来设置这个弹框的事件、动画、属性及view的操作,所以我们一开始要扩展Dialog的功能,就可以在这里面配置编写相应的方法,在Builder内部的方法一般是可以链式操作的,达到灵活配置的作用。

    在Builder静态内部类外部的方法除了构造函数以外,其他的方法应该是可以直接通过自定义的Dialog获取到的属性或事件等,这些操作大多是单个操作,主要是获取某些属性或者事件,比如获取Dialog中View的某个控件等。

    好了 ,PowerFulDialog这个类的组成大概就是这个样子。下面我们来说说直接和弹框打交道的控制类PowerfulController.class


    2.编写一个控制类(统一配置事件、参数和弹窗属性)

    2.1 为什么要编写一个控制类
    • 增强代码可读性,逻辑清晰,该类主要是设置弹框属性和通过辅助类设置弹框内容。而PowerFulDialog类主要是负责去操作Dialog的状态(create show dismiss...)

    • 方便后期需求扩展,增加属性时候从一定程度上减少代码间的耦合度

    2.2 如何编写这个控制类
    • 首先我们可以参考一下源码,在这个类里面我们会写一些必要的参数,被传入的当前需要显示的Dialog、当前Dialog需展示的Window、还有一个就是我们为了专门处理View而新建的一个ViewHelper辅助类,并且会写一些设置view相关的方法给AlertParams内部类调用。

    • 其次,看AlertController源码我们也可以发现,我们会在PowerfulController这个类里面建立一个静态内部类AlertParams,作用主要是用来存放需要配置的参数属性(显示的view、上下文、文字、颜色、大小、位置等),一个Dialog能够显示的基本属性条件基本上都必须写在这个内部类里面。

    • 在内部类方法apply中,我们会传当前Dialog的Controller 对象,然后去设置这个传进来的Controller对象,并获取到当前处理view的辅助类,并完成view的处理。

    • 那么,什么时候应该调用这个apply方法呢,上面说过了,apply方法是去给这个Dialog的Controller(控制器)下达配置Dialog(包括设置view、大小、字体、位置等)的指令,所以,这个方法应该是我们在Dialog执行create方法的时候调用。链式配置完成后调用create方法,我们就可以得到我们配置后建造出来的Dialog了。

    • 有的人就有疑问了,链式表达后有的直接用show也可以配置啊。如果真的有这样的人,我劝你还是多看看源码,再出来装逼,其实它也还是走了这一步的,如下图。

    show之前也执行了create.png

    3.编写一个View辅助类(用于实际直接操作view)

    2.1 为什么要编写一个辅助类
    • 增强代码可读性,逻辑清晰,该类主要是获取到指定view并直接设置View的属性,而PowerfulController主要是设置整个window的状态、动画、位置等,还有操控辅助类设置view

    • 方便后期需求扩展,增加属性时候从一定程度上减少代码间的耦合度

    2.2 如何编写一个辅助类
    • 既然该类的主要作用是用于设置view的属性,那么必不可少的参数有Context、View(或者布局ID),所以构造函数需要传递这两个参数,这个类内部应该提供设置view的方法。DialogViewHelper.class代码如下:
    private View mDialogView=null;
    //防止泄露
    private SparseArray<WeakReference<View>> mViews;
    
    public DialogViewHelper(Context mContext, int mViewLayoutResId) {
        this();
        mDialogView= LayoutInflater.from(mContext).inflate(mViewLayoutResId,null);
    }
    
    public DialogViewHelper() {
        mViews=new SparseArray<>();
    }
    
    /**
     * 设置布局
     * @param mView
     * */
    public void setDialogView(View mView) {
        this.mDialogView=mView;
    }
    
    /**
     * 设置文本
     * @param viewId
     * @param text
     */
    public void setText(int viewId, CharSequence text) {
    
        TextView tv=getView(viewId);
        if(tv!=null){
            tv.setText(text==null?"":text);
        }
    
    }
    
    /**
     * 设置文本颜色
     * @param viewId
     * @param colorRes
     */
    public void setTextColor(Context context, int viewId, int colorRes) {
    
        TextView tv=getView(viewId);
        if(tv!=null){
            tv.setTextColor(context.getResources().getColor(colorRes==0? R.color.text_gray_normal:colorRes));
        }
    
    }
    
    /**
     * 设置图片
     * @param viewId
     * @param imgRes
     */
    public void setImage(int viewId, int imgRes) {
    
        ImageView iv=getView(viewId);
        if(iv!=null){
            iv.setImageResource(imgRes==0? R.mipmap.default_head:imgRes);
        }
    
    }
    /**
     * 设置view的显示和隐藏
     * @param viewId
     * @param visibilityMode
     */
    public void setVisiable(int viewId, int visibilityMode) {
        View v=getView(viewId);
        if(v!=null){
            v.setVisibility(visibilityMode);
        }
    }
    /**
     * 优化findViewById
     * @param viewId
     * @param <T>
     * @return
     */
    public  <T extends View> T getView(int viewId) {
        View v=null;
        WeakReference<View> viewRefrence=mViews.get(viewId);
        if(viewRefrence!=null){
            v=viewRefrence.get();
        }
        if(v==null){
            v=mDialogView.findViewById(viewId);
            if(v!=null){
                mViews.put(viewId,new WeakReference<View>(v));
            }
        }
        return (T)v;
    }
    
    /**
     * 设置点击事件
     * @param viewId
     * @param onClickListener
     */
    public void setOnclickListener(int viewId, View.OnClickListener onClickListener) {
        View v=getView(viewId);
        if(v!=null){
            v.setOnClickListener(onClickListener);
        }
    
    }
    
    public View getDialogView() {
        return mDialogView;
    }
    

    总结

    1.做一个“懒”的程序员
    • 这里的懒,不是指整天喜欢装逼,不干事儿,不敲代码,不在实践中求真知的那个懒,指的是,会用简短高效的代码,去完成项目中的需求。
    • “用最少的代码实现最牛逼的效果”相信这是每个“懒”程序员的终极目标,所以,我们平时在项目实际开发中,多去从中思考,有没有更加优秀的实现方式,多尝试,多积累。
    2.时刻想着这段代码是否还可以被优化
    • 在这个demo编写过程中,大家可以看到,有很多地方用到了SparseArray和弱引用。
    • 其中我们主要是用SparseArray这个集合去存储已经从Dialog中取到的view id和对应的值,刚好利用view id为int类型,使用SparseArray更加节约内存,从而提高性能,当某个拥有id的view通过findviewbyid被查询到后都会放入集合,下次使用时不会再经过findviewbyid而是直接从SparseArray去取出,如若没有查询到该id则又通过findviewbyid查询,这样就提高了性能,减少了内存开支。
    • 使用弱引用主要是为了系统内存不足时候造成内存泄漏的问题。

    老司机带你理解SparseArray:Android内存优化(使用SparseArray和ArrayMap代替HashMap)
    老司机带你理解为什么要使用弱引用:Java 理论与实践
    用弱引用堵住内存泄漏)

    3.要善于去学习源码
    • 讲真,本文demo其实就是一个源码扩展和优化版本,其实其中的思维有百分之七八十的相似,无论是设计模式还是实现思路。所以,多看源码,你会学到很多东西的。

    在线查看Android源码地址:在线查看android源码

    4.最后奉上我的demo
    • 最近上传到的GitHub,未来会长期更新和完善,如果有什么好的建议或者问题,欢迎提issue。
    • 在简书的第一篇文章,如果写得有不好的地方或有误的地方,还希望指正,在下感激不尽。
    • 最后,欢迎大家star and fork。

    一个万能的Dialog demo地址:PowerfulDialog,为程序猿而生


    请点赞,因为您的鼓励将是我写作的最大动力!

    相关文章

      网友评论

      本文标题:如何从0构造出一个万能的Dialog

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