likes
comments
collection
share

Android AlertDialog为什么点击按钮完就会消失,以及解决。

作者站长头像
站长
· 阅读数 6

场景

遇到问题一般都是因为存在使用场景和工具不匹配,有的人可能从来没有遇到过这个问题。

研究这个问题的主要场景是,AlertDialog做为Android官方给予的比较方便的带按钮消息弹窗。

有些人会喜欢用AlertDialog做一些动态化的弹窗组件,通过setView动态插入需要显示使用的布局到AlertDialog中,或者原本就支持的setItems()多选模式,如果涉及到一些人为操作,就有可能需要二次提示或者防误触。

比如用AlertDialog做多选弹窗,如果一项也没选,按产品需求,点击确认之后,应该是提示用户至少选择一项,但AlertDialog会直接关闭,需要用户重新操作打开新的选择,这很不合理。

首先从AlertDialog看起

AlertDialog部分源码:

protected AlertDialog(@NonNull Context context) {
    this(context, 0);
}

/**
 * Construct an AlertDialog that uses an explicit theme.  The actual style
 * that an AlertDialog uses is a private implementation, however you can
 * here supply either the name of an attribute in the theme from which
 * to get the dialog's style (such as {@link R.attr#alertDialogTheme}.
 */
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);
}

点进AlertDialog可以看到,它有三个构造函数,其中AlertDialog(@NonNull Context context) 和AlertDialog(@NonNull Context context, boolean cancelable, @Nullable OnCancelListener cancelListener) 中都调用了this(context, 0);也就是中间的AlertDialog(@NonNull Context context, @StyleRes int themeResId) 方法。也就是初始化了AlertController对象。

AlertController.class

构造函数


    Handler mHandler;
    
    public AlertController(Context context, AppCompatDialog di, Window window) {
        
        mHandler = new ButtonHandler(di);
        
    }
    

AlertController的构造函数中有很多初始化和属性值获取,但最显眼也是最关键的就是这个ButtonHandler的初始化。

ButtonHandler


    private static final class ButtonHandler extends Handler {
        // Button clicks have Message.what as the BUTTON{1,2,3} constant
        private static final int MSG_DISMISS_DIALOG = 1;

        private WeakReference<DialogInterface> mDialog;

        public ButtonHandler(DialogInterface dialog) {
            mDialog = new WeakReference<>(dialog);
        }

        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {

                case DialogInterface.BUTTON_POSITIVE:
                case DialogInterface.BUTTON_NEGATIVE:
                case DialogInterface.BUTTON_NEUTRAL:
                    ((DialogInterface.OnClickListener) msg.obj).onClick(mDialog.get(), msg.what);
                    break;

                case MSG_DISMISS_DIALOG:
                    ((DialogInterface) msg.obj).dismiss();
            }
        }
    }

可以看到,handleMessage中,接收到MSG_DISMISS_DIALOG消息时,便会触发dismiss()方法从而使AlertDialog关闭。

那是什么时候发送的MSG_DISMISS_DIALOG消息呢?

我们在AlertController.class中搜索MSG_DISMISS_DIALOG或者ctrl检查它的使用,发现了下列地方被使用。一个点击监听,,任意按钮点击之后,都会触发最后的mHandler.obtainMessage(ButtonHandler.MSG_DISMISS_DIALOG, mDialog) .sendToTarget();导致了AlertDialog的关闭。

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

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

        // Post a message so we dismiss after the above handlers are executed
        mHandler.obtainMessage(ButtonHandler.MSG_DISMISS_DIALOG, mDialog)
                .sendToTarget();
    }
};

在AlertController.class中查找mButtonHandler的使用,可以发现一个叫做setupButtons()的方法,里面声明了三个基础按钮的点击事件,也就是说,其实AlertDialog中的按钮在不改变的情况下,是默认就有点击事件的。

    mButtonPositive = (Button) buttonPanel.findViewById(android.R.id.button1);
    mButtonPositive.setOnClickListener(mButtonHandler);
    
    mButtonNegative = buttonPanel.findViewById(android.R.id.button2);
    mButtonNegative.setOnClickListener(mButtonHandler);

    mButtonNeutral = (Button) buttonPanel.findViewById(android.R.id.button3);
    mButtonNeutral.setOnClickListener(mButtonHandler);

再一路往上找方法,setupButtons()是在setupView()中被调用,setupView()是在installContent()中被调用。

而installContent则是在AlertDialog的onCreate方法中被调用,到这里也就实现了逻辑闭环。

解决

找到了源码关键之后,解决思路也很简单,我们在builder之后,重新给按钮设置一个新的点击监听,覆盖掉初始化中默认的监听逻辑,这样新的点击方法里不包含dismiss的触发,也就不会导致AlertDialog关闭。

    AlertDialog.Builder builder = new AlertDialog.Builder(context);
    //......
    builder.setPositiveButton(R.string.sign_positive_text, null);
    final AlertDialog dialog = builder.create();
    dialog.show();
    dialog.getButton(DialogInterface.BUTTON_POSITIVE).setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    //TODO
                }
    });