建造者模式打造隨心所欲的Android對話方塊

Smilyyy發表於2017-04-15

可能有人要說不就是自定義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,然後為其設定內容或者監聽事件。

專案連結

相關文章