AlertDialog点击确定必定会dismiss问题

AlertDialog点击确定必定会dismiss问题

场景:

对于业务处理,需要在点击AlertDialog的确定按钮之后做判断,如果满足要求,弹窗消失,反之,在弹窗上做特殊的错误处理,弹窗不消失。

首先,弹窗部分使用原生的AlertDialog.Builder来构建的。因此,最开始我觉得这个功能只需要设置对应确定按钮即可–setPositiveButton(text, positiveClickListener);

		positiveClickListener = new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                // 逻辑判断
                ........
                if (条件不满足) {
                    return;
                }
                dialog.dismiss();
            }
        }

最开始的理解,是AlertDialog需要在点击确定的时候手动dismiss掉。所以在调用dialog.dismiss()之前进行逻辑判断,不满足条件在dismiss之前返回即可。但是后续实际测试时发现,不论何时return,只要点击了确定按钮,dialog都会dismiss。

解决方案:

先说明下解决方案:

网络上有很多说用放射的方式,实际上没有必要,只要拿到对应的弹窗确定按钮,然后手动设置点击响应即可(注意,需要在Dialog show之后进行设置);

// dialog 未对应AlertDialog实例对象;
dialog.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener(v -> {
    // 逻辑确认
    ........
    if (不满足条件) {
        return;
    }
    dialog.dismiss();
}

原理解析:

从现象上看:我们对于DialogInterface.OnClickListener这个监听的设置不生效,所以得从源码看下:

AlertDialog.Builder builder = new AlertDialog.Builder(context)
                .setView(dialogLayout)
                .setCancelable(cancelable)
                .setPositiveButton(positiveText, onPositiveClickListener)
                .setNegativeButton(negativeText, onNegativeClickListener);

通常来说,对应AlertDialog的创建如上所示,使用Builder进行构建时,会先设置参数:

        public Builder(@NonNull Context context, @StyleRes int themeResId) {
            this.P = new AlertParams(new ContextThemeWrapper(context, AlertDialog.resolveDialogTheme(context, themeResId)));
            this.mTheme = themeResId;
        }

        public AlertDialog.Builder setView(View view) {
            this.P.mView = view;
            this.P.mViewLayoutResId = 0;
            this.P.mViewSpacingSpecified = false;
            return this;
        }

        public AlertDialog.Builder setPositiveButton(CharSequence text, OnClickListener listener) {
            this.P.mPositiveButtonText = text;
            this.P.mPositiveButtonListener = listener;
            return this;
        }

通过Builder进行构造,实际上只是将数据存储在其AlertParams中,后面实际起作用是在Builder.create()或Builder.show():

        public AlertDialog show() {
            AlertDialog dialog = this.create();
            dialog.show();
            return dialog;
        }
        
        public AlertDialog create() {
            AlertDialog dialog = new AlertDialog(this.P.mContext, this.mTheme);
            this.P.apply(dialog.mAlert);
            dialog.setCancelable(this.P.mCancelable);
            if (this.P.mCancelable) {
                dialog.setCanceledOnTouchOutside(true);
            }

            dialog.setOnCancelListener(this.P.mOnCancelListener);
            dialog.setOnDismissListener(this.P.mOnDismissListener);
            if (this.P.mOnKeyListener != null) {
                dialog.setOnKeyListener(this.P.mOnKeyListener);
            }

            return dialog;
        }

实际上对于Builder.show会在内部调用create,先创建一个AlertDialog对象,然后调用Dialog的show方法进行展示。

在create()方法中,首先创建一个AlertDialog对象,然后调用AlertParams.apply(),后面都是对Dialog监听的设置,这里没有涉及到确定/取消按钮的,所以先不深入;

在apply()中会传递一个AlertControl对象,它是在AlertDialog构造函数中被创建的:

    protected AlertDialog(@NonNull Context context, @StyleRes int themeResId) {
        super(context, resolveDialogTheme(context, themeResId));
        this.mAlert = new AlertController(this.getContext(), this, this.getWindow());
    }

public void apply(AlertController dialog) {
            ........
            if (this.mMessage != null) {
                dialog.setMessage(this.mMessage);
            }

            if (this.mPositiveButtonText != null || this.mPositiveButtonIcon != null) {
                dialog.setButton(-1, this.mPositiveButtonText, this.mPositiveButtonListener, (Message)null, this.mPositiveButtonIcon);
            }

            if (this.mNegativeButtonText != null || this.mNegativeButtonIcon != null) {
                dialog.setButton(-2, this.mNegativeButtonText, this.mNegativeButtonListener, (Message)null, this.mNegativeButtonIcon);
            }

            if (this.mNeutralButtonText != null || this.mNeutralButtonIcon != null) {
                dialog.setButton(-3, this.mNeutralButtonText, this.mNeutralButtonListener, (Message)null, this.mNeutralButtonIcon);
            }

            if (this.mItems != null || this.mCursor != null || this.mAdapter != null) {
                this.createListView(dialog);
            }
    		........

        }

只看相关部分,也就是dialog.setButton(-1, this.mPositiveBu…);部分

    public void setButton(int whichButton, CharSequence text, android.content.DialogInterface.OnClickListener listener, Message msg, Drawable icon) {
        if (msg == null && listener != null) {
            // 这里的mHandler实际上是在AlertController构造中创建的
            // this.mHandler = new AlertController.ButtonHandler(di);
            // 传递了一个DialogInterface对象进去,
            msg = this.mHandler.obtainMessage(whichButton, listener);
        }

        switch(whichButton) {
        case -3:
            this.mButtonNeutralText = text;
            this.mButtonNeutralMessage = msg;
            this.mButtonNeutralIcon = icon;
            break;
        case -2:
            this.mButtonNegativeText = text;
            this.mButtonNegativeMessage = msg;
            this.mButtonNegativeIcon = icon;
            break;
        case -1:
            // 将前面构建的Message赋值给AlertController的mButtonPositiveMessage字段
            // 对应的确定按钮监听放在message中。
            this.mButtonPositiveText = text;
            this.mButtonPositiveMessage = msg;
            this.mButtonPositiveIcon = icon;
            break;
        default:
            throw new IllegalArgumentException("Button does not exist");
        }

    }

到这,实际上AlertDialog的创建就完成了,我们还没明白为什么弹窗一定会消失,只能说这个确定按钮的监听在Message中,后面肯定是通过handler机制进行消息传递的。

接着看,Dialog.show()方法:

   // Dialog.java中代码
	public void show() {
        ........
            if (!mCreated) {
                dispatchOnCreate(null);
            } else {
        ........
    }
    
    void dispatchOnCreate(Bundle savedInstanceState) {
        if (!mCreated) {
            onCreate(savedInstanceState);
            mCreated = true;
        }
    }
        
    protected void onCreate(Bundle savedInstanceState) {
    }   

在show()中,第一次创建的时候会调用dispatchOnCreate()->onCreate()但是,在Dialog中onCreate的实现为空,所以它应该在子类中有Override:

    // AlertDialog extends Dialog
	@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // mAlert是一个AlertController对象
        mAlert.installContent();
    }

// AlertController.java
	public void installContent() {
        int contentView = this.selectContentView();
        this.mDialog.setContentView(contentView);
        this.setupView(); // 相关按钮代码在setupView中
    }

    private void setupButtons(ViewGroup buttonPanel) {
        int BIT_BUTTON_POSITIVE = true;
        int BIT_BUTTON_NEGATIVE = true;
        int BIT_BUTTON_NEUTRAL = true;
        int whichButtons = false;
        this.mButtonPositive = (Button)buttonPanel.findViewById(16908313);
        this.mButtonPositive.setOnClickListener(this.mButtonHandler);
        ........
    }

setupView()->setupButtons();在设置按钮方法张,先从ViewGroup中拿到对应的确认按钮,然后设置按钮监听,这里传递进去的是一个OnClickListener对象,在AlertController的全局字段中:

Handler mHandler;
    private final OnClickListener mButtonHandler = new OnClickListener() {
        public void onClick(View v) {
            Message m;
            if (v == AlertController.this.mButtonPositive && AlertController.this.mButtonPositiveMessage != null) {
                m = Message.obtain(AlertController.this.mButtonPositiveMessage);
            } else if (v == AlertController.this.mButtonNegative && AlertController.this.mButtonNegativeMessage != null) {
                m = Message.obtain(AlertController.this.mButtonNegativeMessage);
            } else if (v == AlertController.this.mButtonNeutral && AlertController.this.mButtonNeutralMessage != null) {
                m = Message.obtain(AlertController.this.mButtonNeutralMessage);
            } else {
                m = null;
            }

            if (m != null) {
                m.sendToTarget();
            }

            AlertController.this.mHandler.obtainMessage(1, AlertController.this.mDialog).sendToTarget();
        }
    };

所以,当我们点击确认按钮时,会调用mButtonHandler这个监听,判断时mButtonPositive这个View时,从AlertController中拿mButtonPositiveMessage这个变量,它是在setButton中被赋值的,其中的msg.obj就是我们在AlertDialog.Builder.setPositiveButton(listener)中传递进来的一个DialogInterface.OnClickListener对象。

拿到消息后,先调用Message.sendToTarget,根据Handler-Message的消息机制,发送消息后,对应处理消息回调在Handler中,而这里的Handler就是mHandler:this.mHandler = new AlertController.ButtonHandler(di);

    private static final class ButtonHandler extends Handler {
        private static final int MSG_DISMISS_DIALOG = 1;
        private WeakReference<DialogInterface> mDialog;

        public ButtonHandler(DialogInterface dialog) {
            this.mDialog = new WeakReference(dialog);
        }

        public void handleMessage(Message msg) {
            switch(msg.what) {
            case -3:
            case -2:
            case -1:
                ((android.content.DialogInterface.OnClickListener)msg.obj).onClick((DialogInterface)this.mDialog.get(), msg.what);
            case 0:
            default:
                break;
            case 1:
                ((DialogInterface)msg.obj).dismiss();
            }

        }
    }

所以在处理消息逻辑中,先是从msg中拿出之前保存的DialogInterface.OnClickListener对象,然后调用其onClick,也就是执行我们自己写的确认回调;

到此位置,点击确认按钮源码流程就理解通了,但是,为什么会消失呢?

我们回到之前的按钮的监听流程中:

			if (m != null) {
                m.sendToTarget();
            }

            AlertController.this.mHandler.obtainMessage(1, AlertController.this.mDialog).sendToTarget();

在调用m.sendToTarget()执行自定义监听之后,还会发送一个消息,其中msgWhat = 1;msg.obj = AppCompatDialog;

所以就算我们之前自定义监听中,判断不满足条件,直接return之后,也会执行到ButtonHandler.handleMessage中的case 1,所以必定会走Dialog.dismiss();

而我们做的,就是在AlertDialog显示之后,通过getButton拿到确认按钮实例,然后自行设置OnClickListener,覆盖掉之前在Dialog::show()->Dialog::dispatchOnCreate()->AlertDialog::onCreate()->AlertController::installContent()->AlertController::setupView()->AlertController::setupButtons()->mButtonPositive.setOnClickListener();中设置的mButtonHandler。将这个监听覆盖掉之后,就不会走后续DialogInterface.OnClickListener监听的流程了,而是直接走的View.OnClickListener流程;

所以,上面提到的“必须在弹窗show()之后进行监听设置”,也是因为只有调用show之后,才能获取到对应的窗口中Buttom实例(此时View被从xml中解析,并实例化),并且在此之后,我们设置的监听才能覆盖掉原有show流程中设置的监听。

当你在AlertDialog上调用`show()`方法时,它会返回一个Dialog对象,你可以使用它来监听对话框上的按钮点击事件。 例如: ``` AlertDialog dialog = new AlertDialog.Builder(this) .setTitle("标题") .setMessage("消息") .setPositiveButton("确定", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { // 点击确定按钮时的逻辑 } }) .setNegativeButton("取消", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { // 点击取消按钮时的逻辑 } }) .create(); dialog.show(); ``` 在上面的示例中,我们在AlertDialog上设置了两个按钮:确定和取消。当用户点击其中一个按钮时,会分别触发相应的`onClick()`方法。 因此,你可以在点击按钮时执行所需的操作,然后在`onClick()`方法中关闭对话框并返回。例如: ``` AlertDialog dialog = new AlertDialog.Builder(this) .setTitle("标题") .setMessage("消息") .setPositiveButton("确定", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { // 点击确定按钮时的逻辑 // 关闭对话框并返回 dialog.dismiss(); finish(); } }) .setNegativeButton("取消", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { // 点击取消按钮时的逻辑 // 关闭对话框并返回 dialog.dismiss(); finish(); } }) .create(); dialog.show(); ``` 在上面的示例中,我们在点击按钮时关闭了对话框并调用了`finish()`方法来返回。当你调用`finish()`方法时,当前Activity会被销毁并返回上一个Activity。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值