红橙Darren视频笔记 万能Dialog builder设计模式

1.Android原生的AlertDialog

我们今天看一下AlertDialog的创建方式以及它使用到的builder设计模式
我们先看看原生Android的AlertDialog创建方式:

                AlertDialog alertDialog = new AlertDialog.Builder(MainActivity.this)
                        .setTitle("标题")
                        .setNegativeButton("取消", (dialog, which) -> {
                            dialog.dismiss();
                            Toast.makeText(MainActivity.this, "点击了取消", Toast.LENGTH_SHORT).show();
                        })
                        .setPositiveButton("确定", (dialog, which) -> Toast.makeText(MainActivity.this, "点击了确定", Toast.LENGTH_SHORT).show())
                        .setCancelable(false)
                        .setIcon(R.drawable.ic_launcher_background)
                        .setMessage("这是消息这是消息这是消息这是消息")
                        .setView(button)
                        .create();
                alertDialog.show();

创建效果
在这里插入图片描述
如果理解了Builer设计模式 那么以上的代码是很好理解的,关于Builder设计模式 我之前也有过总结,不过不是很容易懂,找到一篇更好理解的博客:
https://www.jianshu.com/p/afe090b2e19c
简单来说 Builder设计模式用于构建复杂对象,一般用于该对象有很多可有可无的参数
一般该设计模式分为以下几个部分
Director:创建者 用于获取要创建的对象(Product)
Builder:一般定义为接口(如果有继承的子类的话),定义了产品的设置接口,可以创建产品
实际Builder:(可有可无 取决于系统有多复杂)Builder的继承者,实际的Builder
Product:需要创建的目标对象
在这里插入图片描述
将AlertDialog套用到以上这个模板那么有如下结果
AlertDialog代表产品(Product)
AlertDialog.Builder代表builder
AlertController代表Director 用于控制产品具体显示哪些部分
让我们再细看一下AlertDialog的源码 分析这三者的结构

2.Android原生AlertDialog源码结构

AlertDialog含有内部类Builder
AlertDialog在调用create创建AlertDialog对象的时候 实际调用的是AlertController的create方法
AlertController内部还有个关键的内部类AlertParams
从上面的demo可以看出AlertDialog的使用顺序大概是
1.调用各种set方法
2.调用create方法
3.调用show方法
在这里插入图片描述
调用set方法是将各种参数设置进Builder的内部属性AlertParams中去
调用create方法时 创建了AlertController对象 同时将之前设置的属性应用到dialog中
最后调用show方法
让我们看一下源码的调用顺序
首先调用new AlertDialog.Builder(MainActivity.this)创建builder对象 同时创建AlertParams的实例P

        public Builder(Context context) {
            this(context, resolveDialogTheme(context, ResourceId.ID_NULL));
        }

        public Builder(Context context, int themeResId) {
            //这里创建了AlertParams的实例P
            P = new AlertController.AlertParams(new ContextThemeWrapper(
                    context, resolveDialogTheme(context, themeResId)));
        }

第二步调用builder的各种set方法 比如

        public Builder setTitle(CharSequence title) {
            P.mTitle = title;
            return this;
        }

        public Builder setCancelable(boolean cancelable) {
            P.mCancelable = cancelable;
            return this;
        }

        public Builder setIcon(@DrawableRes int iconId) {
            P.mIconId = iconId;
            return this;
        }

可以看到虽然这些是builder的方法 但是实际操作的却是AlertParams的实例P 即AlertParams用来存放Dialog的各种参数和style
第三步调用builder的create方法

    public AlertDialog create() {
        // Context has already been wrapped with the appropriate theme.
        final AlertDialog dialog = new AlertDialog(P.mContext, 0, false);//跟下去
        P.apply(dialog.mAlert);//应用之前设置在AlertController.AlertParams的各种参数
        dialog.setCancelable(P.mCancelable);//应用之前设置在AlertController.AlertParams的各种参数
        if (P.mCancelable) {
            dialog.setCanceledOnTouchOutside(true);
        }
        dialog.setOnCancelListener(P.mOnCancelListener);
        dialog.setOnDismissListener(P.mOnDismissListener);
        if (P.mOnKeyListener != null) {
            dialog.setOnKeyListener(P.mOnKeyListener);
        }
        return dialog;
    }

    AlertDialog(Context context, @StyleRes int themeResId, boolean createContextThemeWrapper) {
        super(context, createContextThemeWrapper ? resolveDialogTheme(context, themeResId) : 0,
                createContextThemeWrapper);

        mWindow.alwaysReadCloseOnTouchAttr();
        mAlert = AlertController.create(getContext(), this, getWindow());//创建AlertController对象
    }

    public static final AlertController create(Context context, DialogInterface di, Window window) {
        final TypedArray a = context.obtainStyledAttributes(
                null, R.styleable.AlertDialog, R.attr.alertDialogStyle, 0);
        int controllerType = a.getInt(R.styleable.AlertDialog_controllerType, 0);
        a.recycle();

        switch (controllerType) {
            case MICRO:
                return new MicroAlertController(context, di, window);
            default:
                return new AlertController(context, di, window);//我这里走的这个case
        }
    }

	//设置各种style和layout
    protected AlertController(Context context, DialogInterface di, Window window) {
        mContext = context;
        mDialogInterface = di;
        mWindow = window;
        mHandler = new ButtonHandler(di);

        final TypedArray a = context.obtainStyledAttributes(null,
                R.styleable.AlertDialog, R.attr.alertDialogStyle, 0);

        mAlertDialogLayout = a.getResourceId(
                R.styleable.AlertDialog_layout, R.layout.alert_dialog);
        mButtonPanelSideLayout = a.getResourceId(
                R.styleable.AlertDialog_buttonPanelSideLayout, 0);
        mListLayout = a.getResourceId(
                R.styleable.AlertDialog_listLayout, R.layout.select_dialog);

        mMultiChoiceItemLayout = a.getResourceId(
                R.styleable.AlertDialog_multiChoiceItemLayout,
                R.layout.select_dialog_multichoice);
        mSingleChoiceItemLayout = a.getResourceId(
                R.styleable.AlertDialog_singleChoiceItemLayout,
                R.layout.select_dialog_singlechoice);
        mListItemLayout = a.getResourceId(
                R.styleable.AlertDialog_listItemLayout,
                R.layout.select_dialog_item);
        mShowTitle = a.getBoolean(R.styleable.AlertDialog_showTitle, true);

        a.recycle();

        /* We use a custom title so never request a window title */
        window.requestFeature(Window.FEATURE_NO_TITLE);
    }

第四步调用show方法

3.自己写一个万能Dialog

思路 按照之前分析的 builder的构造方法,设置属性,create,show等顺序 参考原生Android的代码进行代码编写

代码展示

public class AlertDialog extends Dialog implements DialogInterface {
    private final AlertController mAlert;

    //protected方法 让外部无法直接new对象 只能通过Builder创建对象
    protected AlertDialog(@NonNull Context context, int themeResId) {
        super(context, themeResId);
        mAlert = new AlertController(this, getWindow());
    }

    //通过AlertDialog的引用可以setText 虽然和builder中的setText方法最终调用的都是AlertController的方法
    //但是builder 一般在初次创建的时候才会使用 如果后期需要变化文字 可以通过AlertDialog的引用设置
    public void setText(int viewId, CharSequence text) {
        mAlert.setText(viewId,text);
    }

    //可以通过AlertDialog的引用根据viewId获取指定的view
    public <T extends View> T getView(int viewId) {
        return mAlert.getView(viewId);
    }

    //可以通过AlertDialog的引用设置点击事件
    public void setOnclickListener(int viewId, View.OnClickListener listener) {
        mAlert.setOnclickListener(viewId,listener);
    }


    public static class Builder {
        private final AlertController.AlertParams P;

        //各种set方法 都是直接将参数保存到AlertParams中
        //存储标题
        public AlertDialog.Builder setTitle(CharSequence title) {
            P.mTitle = title;
            return this;
        }

        //存储标题方式2
        public AlertDialog.Builder setTitle(@StringRes int titleId) {
            P.mTitle = P.mContext.getText(titleId);
            return this;
        }

        //存储消息体
        public AlertDialog.Builder setMessage(@StringRes int messageId) {
            P.mMessage = P.mContext.getText(messageId);
            return this;
        }

        //存储dialog的布局
        public Builder setContentView(View view) {
            P.mView = view;
            P.mViewLayoutResId = 0;
            return this;
        }

        //另一种方法存储dialog的布局
        public Builder setContentView(int layoutId) {
            P.mView = null;
            P.mViewLayoutResId = layoutId;
            return this;
        }

        //和通过AlertDialog的引用setText的最终方式一样
        //这里只是存储变量
        public Builder setText(int viewId,CharSequence text){
            P.mTextArray.put(viewId,text);
            return this;
        }

        //存储dialog取消的listener
        public AlertDialog.Builder setOnCancelListener(DialogInterface.OnCancelListener onCancelListener) {
            P.mOnCancelListener = onCancelListener;
            return this;
        }

        //存储dialog的宽度
        public Builder fullWidth(){
            P.mWidth = ViewGroup.LayoutParams.MATCH_PARENT;
            return this;
        }

        //存储是否从底部弹出 并且是否使用动画
        public Builder formBottom(boolean isAnimation){
            if(isAnimation){
                P.mAnimations = R.style.dialog_from_bottom_anim;
            }
            P.mGravity = Gravity.BOTTOM;
            return this;
        }

        //存储dialog的宽高
        public Builder setWidthAndHeight(int width, int height){
            P.mWidth = width;
            P.mHeight = height;
            return this;
        }

        //存储dialog的默认动画
        public Builder addDefaultAnimation(){
            P.mAnimations = R.style.dialog_scale_anim;
            return this;
        }

        //存储dialog的动画
        public Builder setAnimations(int styleAnimation){
            P.mAnimations = styleAnimation;
            return this;
        }

        //存储dialog点击空白区域是否可以取消
        public Builder setCancelable(boolean cancelable) {
            P.mCancelable = cancelable;
            return this;
        }

        //存储dialog消失的监听
        public Builder setOnDismissListener(OnDismissListener onDismissListener) {
            P.mOnDismissListener = onDismissListener;
            return this;
        }

        //存储dialog键值监听
        public Builder setOnKeyListener(OnKeyListener onKeyListener) {
            P.mOnKeyListener = onKeyListener;
            return this;
        }
        //end各种set方法

        //构造方法
        public Builder(Context context) {
            this(context, R.style.dialog);
        }

        //构造方法 带有主题
        public Builder(Context context, int themeResId) {
            P = new AlertController.AlertParams(context, themeResId);
        }

        //创建dialog对象
        public AlertDialog create() {
            // Context has already been wrapped with the appropriate theme.
            final AlertDialog dialog = new AlertDialog(P.mContext, P.mThemeResId);
            P.apply(dialog.mAlert);//将AlertParams中的参数应用到dialog中 间接应用 (将参数传递给AlertController 接着还有可能通过DialogViewHelper最终影响dialog)
            dialog.setCancelable(P.mCancelable);//将AlertParams中的参数应用到dialog中 直接应用
            if (P.mCancelable) {
                dialog.setCanceledOnTouchOutside(true);//将AlertParams中的参数应用到dialog中 直接应用
            }
            dialog.setOnCancelListener(P.mOnCancelListener);//将AlertParams中的参数应用到dialog中 直接应用
            dialog.setOnDismissListener(P.mOnDismissListener);//将AlertParams中的参数应用到dialog中 直接应用
            if (P.mOnKeyListener != null) {
                dialog.setOnKeyListener(P.mOnKeyListener);//将AlertParams中的参数应用到dialog中 直接应用
            }
            return dialog;
        }

        //显示方法
        public AlertDialog show() {
            final AlertDialog dialog = create();
            dialog.show();
            return dialog;
        }
    }
}
public class AlertController {

    //AlertController控制的Dialog
    private final AlertDialog mDialog;
    //Dialog显示的window
    private final Window mWindow;
    //辅助类 通过AlertController控制Dialog的布局显示内容 view的事件监听等
    private DialogViewHelper mViewHelper;

    //构造方法在创建dialog对象时调用
    public AlertController(AlertDialog alertDialog, Window window) {
        this.mDialog = alertDialog;
        this.mWindow = window;
    }

    //设置辅助类 在apply AlertController中的属性时调用
    public void setViewHelper(DialogViewHelper viewHelper) {
        this.mViewHelper = viewHelper;
    }

    //外部通过调用controller的该方法setText
    public void setText(int viewId, CharSequence text) {
        //调用DialogViewHelper操作具体的view setText
        mViewHelper.setText(viewId, text);
    }

    //外部通过调用controller的该方法取得view对象
    public <T extends View> T getView(int viewId) {
        //内部调用DialogViewHelper操作具体的view 得到view对象
        return mViewHelper.getView(viewId);
    }

    //外部通过调用controller的该方法set点击事件
    public void setOnclickListener(int viewId, View.OnClickListener listener) {
        //内部调用DialogViewHelper操作具体的view 给view设置点击事件
        mViewHelper.setOnclickListener(viewId, listener);
    }

    //给静态内部类使用的方法
    private AlertDialog getDialog() {
        return mDialog;
    }

    //给静态内部类使用的方法
    private Window getWindow() {
        return mWindow;
    }

    //静态内部类 可以脱离AlertController存在 用于存储dialog的各种变量
    static class AlertParams {
        public CharSequence mTitle;//存储title
        public int mThemeResId;//存储theme
        public boolean mCancelable = true;//默认可以点击空白收起dialog
        public Context mContext;
        public CharSequence mMessage;//存储Message

        //存储各种listener
        public DialogInterface.OnDismissListener mOnDismissListener;
        public DialogInterface.OnKeyListener mOnKeyListener;
        public DialogInterface.OnCancelListener mOnCancelListener;

        //布局和布局id二选一
        public View mView;
        public int mViewLayoutResId;

        //存放int和Object的键值对 比hash map更高效
        //存放各个部分的文字 是id和CharSequence的键值对
        public SparseArray<CharSequence> mTextArray = new SparseArray<>();

        //存储各个控件的点击事件 是viewId和OnClickListener的键值对
        public SparseArray<View.OnClickListener> mClickArray = new SparseArray<>();

        //存储宽度
        public int mWidth = ViewGroup.LayoutParams.WRAP_CONTENT;
        //存储动画的资源id
        public int mAnimations = 0;
        //存储显示位置
        public int mGravity = Gravity.CENTER;
        //存储高度
        public int mHeight = ViewGroup.LayoutParams.WRAP_CONTENT;

        public AlertParams(Context context, int themeResId) {
            this.mContext = context;
            this.mThemeResId = themeResId;
        }

        //将属性直接应用到Dialog或者 通过viewHelper操作Dialog的view
        public void apply(AlertController mAlert) {
            DialogViewHelper viewHelper = null;
            //设置dialog的资源id
            if (mViewLayoutResId != 0) {
                viewHelper = new DialogViewHelper(mContext, mViewLayoutResId);
            }

            //设置dialog的资源id的另一种方式
            if (mView != null) {
                viewHelper = new DialogViewHelper();
                viewHelper.setContentView(mView);
            }

            if (viewHelper == null) {
                throw new IllegalArgumentException("请设置布局view或布局id");
            }

            //真正设置dialog的地方
            mAlert.getDialog().setContentView(viewHelper.getContentView());
            //感觉没什么用 除非外部想要直接操作AlertController类 此前还要提供getViewHelper方法
            mAlert.setViewHelper(viewHelper);

            int textArraySize = mTextArray.size();
            for (int i = 0; i < textArraySize; i++) {
                //调用DialogViewHelper操作具体的view setText
                mAlert.setText(mTextArray.keyAt(i), mTextArray.valueAt(i));
            }

            int clickArraySize = mClickArray.size();
            for (int i = 0; i < clickArraySize; i++) {
                //调用DialogViewHelper操作具体的view setOnclickListener
                mAlert.setOnclickListener(mClickArray.keyAt(i), mClickArray.valueAt(i));
            }

            Window window = mAlert.getWindow();
            // 设置位置
            window.setGravity(mGravity);

            //设置动画
            if (mAnimations != 0) {
                window.setWindowAnimations(mAnimations);
            }

            //设置宽高
            WindowManager.LayoutParams params = window.getAttributes();
            params.width = mWidth;
            params.height = mHeight;
            window.setAttributes(params);
        }
    }
}
class DialogViewHelper {
    private static final String TAG = "DialogViewHelper";
    //dialog的view
    private View mContentView = null;
    //存储了dialog上的各个view viewId+View的键值对 目的是减少findViewById的调用次数
    //为防止内存泄漏 使用WeakReference
    private final SparseArray<WeakReference<View>> mViews = new SparseArray<>();

    public DialogViewHelper(Context context, int layoutResId) {
        mContentView = LayoutInflater.from(context).inflate(layoutResId, null);
    }

    public DialogViewHelper() {
    }

    //根据view id操作该view的Text
    public void setText(int viewId, CharSequence text) {
        // 每次都 findViewById   减少findViewById的次数
        View view = getView(viewId);
        if (view != null) {
            try {
                //利用反射使得所有带有setText(CharSequence)的方法的控件都可以调用setText的方法
                Class clazz = view.getClass();
                Method setTextMethod = clazz.getMethod("setText", CharSequence.class);
                setTextMethod.invoke(view, text);
            } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
                Log.e(TAG, "setText:"+e.getMessage());
                e.printStackTrace();
            }
        }
    }

    //获取view
    public <T extends View> T getView(int viewId) {
        WeakReference<View> viewReference = mViews.get(viewId);
        //利用弱引用防止内存泄漏
        View view = null;
        if (viewReference != null) {
            view = viewReference.get();
        }

        if (view == null) {
            view = mContentView.findViewById(viewId);
            if (view != null) {
                mViews.put(viewId, new WeakReference<>(view));
            }
        }
        return (T) view;
    }

    //操作view setOnClickListener
    public void setOnclickListener(int viewId, View.OnClickListener listener) {
        View view = getView(viewId);
        if (view != null) {
            view.setOnClickListener(listener);
        }
    }

    //存储dialog的view
    public void setContentView(View contentView) {
        this.mContentView = contentView;
    }

    //获取dialog的view
    public View getContentView() {
        return mContentView;
    }
}

使用的方式:

                com.example.dialog.AlertDialog myDialog = new com.example.dialog.AlertDialog.Builder(this)
                        .setContentView(R.layout.detail_comment_dialog)
//                        .setText(R.id.share_label, "评论哈哈")
                        .fullWidth().show();

                // dialog去操作点击事件
                final EditText commentEt = myDialog.getView(R.id.comment_editor);
                myDialog.setOnclickListener(R.id.submit_btn, v12 -> Toast.makeText(MainActivity.this,
                        commentEt.getText().toString().trim(), Toast.LENGTH_LONG).show());
                myDialog.setText(R.id.share_label, "评论哈哈1111");

显示效果
在这里插入图片描述

代码流程分析

AlertDialog AlertController DialogViewHelper一一对应,每一次创建一个AlertDialog 必然有一个唯一AlertController 一个唯一DialogViewHelper被创建
1.new com.example.dialog.AlertDialog.Builder调用时AlertDialog.Builder创建
2.在builder的构造方法中AlertDialog.Builder创建
3.set方法被调用 这些参数被存储在Builder中AlertParams的变量中
4.AlertDialog show方法调用 内部调用create方法 AlertDialog创建
5.AlertDialog构造方法中AlertController创建
6.apply方法调用DialogViewHelper创建 各种参数被应用在dialog上
对比上面Android原生的AlertDialog 可以看到步骤基本一致,或者说是抄的Android源码更合适,哈哈,不过我们现在可以自己更容易决定想要显示成什么样子
再次总结创建顺序和作用
1.AlertDialog.Builder创建 该对象用于规范dialog的显示式样 有哪些API可以调用
2.AlertController.AlertParams创建 该对象用于存储dialog各种参数
3.AlertDialog创建 实际显示的dialog
4.AlertController创建 控制dialog
5.DialogViewHelper创建 直接操作view的辅助类
DialogViewHelper通过AlertController来控制AlertDialog中的view的显示文本以及事件监听等

如何控制Dialog的显示样式?

这里有两种控制方式
一种直接通过AlertController.AlertParams的参数 apply给dialog
比如formBottom

        public Builder formBottom(boolean isAnimation){
            if(isAnimation){
                P.mAnimations = R.style.dialog_from_bottom_anim;
            }
            P.mGravity = Gravity.BOTTOM;
            return this;
        }

上面只是设置参数
在apply函数中有如下调用

            Window window = mAlert.getWindow();
            // 设置位置
            window.setGravity(mGravity);

            //设置动画
            if (mAnimations != 0) {
                window.setWindowAnimations(mAnimations);
            }

另一种 如果涉及到view相关的,则需要通过DialogViewHelper控制
比如setText

    public void setText(int viewId, CharSequence text) {
        mAlert.setText(viewId,text);
    }

调用AlertController的方法

    public void setText(int viewId, CharSequence text) {
        //调用DialogViewHelper操作具体的view setText
        mViewHelper.setText(viewId, text);
    }

最后调用DialogViewHelper的方法 这里无非是找到view 然后判断它有没有setText(CharSequence)的方法 如果有 则调用该方法

    public void setText(int viewId, CharSequence text) {
        // 每次都 findViewById   减少findViewById的次数
        View view = getView(viewId);
        if (view != null) {
            try {
                //利用反射使得所有带有setText(CharSequence)的方法的控件都可以调用setText的方法
                Class clazz = view.getClass();
                Method setTextMethod = clazz.getMethod("setText", CharSequence.class);
                setTextMethod.invoke(view, text);
            } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
                Log.e(TAG, "setText:"+e.getMessage());
                e.printStackTrace();
            }
        }
    }

那么如果我们想扩展这个dialog 则看我们的扩展是否会直接操作view,如果不会,则可以模仿formBottom的方式进行扩展
如果会影响view 则可以通过模仿setText的方式进行扩展

完整代码
https://github.com/caihuijian/learn_darren_eassy_joke/tree/main/baseLibrary/src/main/java/com/example/dialog

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值