Android:如何获得模态对话框或类似的模态行为?

fif*_*fth 53 android synchronization modal-dialog android-activity

这些天我正在研究在Android中模拟模态对话框.我已经google了很多,有很多讨论,但很遗憾没有太多的选择来获得它的模态.这里有一些背景,
对话框,模态对话框和Blockin
对话框/ AlertDialogs:如何在对话框启动时"阻止执行"(.NET风格)

没有直接的方法来获得模态行为,然后我想出了3个可能的解决方案,
1.使用对话框主题的活动,就像这个线程所说的那样,但我仍然无法使主要活动真正等待对话活动返回.主要活动转为停止状态,然后重新启动.
2.构建一个工作线程,并使用线程同步.但是,对于我的应用程序来说,这是一个巨大的重构工作,现在我在主UI线程中有一个主要活动和服务.
3.当存在模态对话框时,在循环内接管事件处理,并在对话框关闭时退出循环.实际上,这是构建一个真正的模态对话框的方式,就像它在Windows中的确切做法一样.我仍然没有这样的原型.

我仍然想用一个以对话为主题的活动模拟它,
1.通过startActivityForResult()启动对话活动
2.从onActivityResult()获取结果
这里有一些来源

public class MainActivity extends Activity {

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    MyView v = new MyView(this);
    setContentView(v);
}

private final int RESULT_CODE_ALERT = 1;
private boolean mAlertResult = false;
public boolean startAlertDialog() {
    Intent it = new Intent(this, DialogActivity.class);
    it.putExtra("AlertInfo", "This is an alert");
    startActivityForResult(it, RESULT_CODE_ALERT);

    // I want to wait right here
    return mAlertResult;
}

@Override
protected void onActivityResult (int requestCode, int resultCode, Intent data) {
    switch (requestCode) {
    case RESULT_CODE_ALERT:
        Bundle ret = data.getExtras();
        mAlertResult = ret.getBoolean("AlertResult");
        break;
    }
}
}
Run Code Online (Sandbox Code Playgroud)

startAlertDialog的调用者将阻止执行并期望返回结果.但是当然,startAlertDialog立即返回,当DialogActivity启动时,主要活动进入STOP状态.

所以问题是,如何使主要活动真正等待结果?
谢谢.

小智 65

使用时我得到了一个模态对话框:

setCancelable(false);
Run Code Online (Sandbox Code Playgroud)

在DialogFragment上(不在DialogBu​​ilder上).

  • 奇迹般有效.示例:final AlertDialog.Builder builder = new AlertDialog.Builder(this).setCancelable(false); (9认同)
  • 这是行不通的。模态对话框的含义是,UI 线程停止工作,直到对话框被关闭,而此代码并非如此 (3认同)
  • 这不会停止代码的执行,它可以防止对话框在按下后被取消....代码可以通过dialogFragment.show(fragmentTransaction, TAG);处的死循环真正停止。并使用 onClickListener 发送广播意图,调用对话框关闭后所需的方法。 (2认同)

Ste*_*han 13

你计划的方式是不可能的.首先,不允许阻止UI线程.您的申请将被终止.其次,需要处理在启动另一个活动时调用的生命周期方法startActivity(在其他活动运行时,您的原始活动将暂停).第三,你可能通过使用startAlertDialog()不是来自UI线程,使用线程同步(如Object.wait())和一些来以某种方式破解它AlertDialog.但是,我强烈建议你不要这样做.它很丑陋,肯定会破坏,而事实并非如此.

重新设计您的方法以捕获这些事件的异步性质.如果你想要一些对话框要求用户进行决定(比如接受或不接受ToS)并根据该决定做特殊操作,请创建一个如下对话框:

AlertDialog dialog = new AlertDialog.Builder(context).setMessage(R.string.someText)
                .setPositiveButton(android.R.string.ok, new OnClickListener() {

                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        dialog.dismiss();
                        // Do stuff if user accepts
                    }
                }).setNegativeButton(android.R.string.cancel, new OnClickListener() {

                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        dialog.dismiss();
                        // Do stuff when user neglects.
                    }
                }).setOnCancelListener(new OnCancelListener() {

                    @Override
                    public void onCancel(DialogInterface dialog) {
                        dialog.dismiss();
                        // Do stuff when cancelled
                    }
                }).create();
dialog.show();
Run Code Online (Sandbox Code Playgroud)

然后有两种方法相应地处理正面或负面反馈(即进行一些操作或完成活动或任何有意义的事情).

  • 我对这个措辞的方式提出质疑"你计划的方式不可能.首先,你不能阻止UI线程." - 无需阻止UI线程显示模态对话框 - 如果Windows阻止了UI线程,Windows也会终止你的应用程序,但它不会阻止它们进行模态对话 - 这些应用程序继续处理消息和在返回到父窗口之前强制用户处理模态对话框时响应(不,父窗口也没有阻塞 - 它们通常处理所有事件). (4认同)
  • 谢谢.我完全同意你上面提到的一切.不幸的是,这对我来说是必须的,而且真实的情况要复杂得多,这是一个很长的故事.模式行为基本上与Android的设计冲突,我们都知道,但是......无论如何,我正在寻找一些优雅的方法来解决它:(解决方案3是一个选项吗? (2认同)

art*_*926 8

Android和iOS的开发人员认为他们是强大而聪明的,足以拒绝Modal Dialog概念(这已经在市场上存在很多年了,之前并没有打扰过任何人),不幸的是我们.

这是我的解决方案,效果很好:

    int pressedButtonID;
    private final Semaphore dialogSemaphore = new Semaphore(0, true);
    final Runnable mMyDialog = new Runnable()
    {
        public void run()
        {
            AlertDialog errorDialog = new AlertDialog.Builder( [your activity object here] ).create();
            errorDialog.setMessage("My dialog!");
            errorDialog.setButton("My Button1", new DialogInterface.OnClickListener() {
                @Override
                public void onClick(DialogInterface dialog, int which) {
                    pressedButtonID = MY_BUTTON_ID1;
                    dialogSemaphore.release();
                    }
                });
            errorDialog.setButton2("My Button2", new DialogInterface.OnClickListener() {
                @Override
                public void onClick(DialogInterface dialog, int which) {
                    pressedButtonID = MY_BUTTON_ID2;
                    dialogSemaphore.release();
                    }
                });
            errorDialog.setCancelable(false);
            errorDialog.show();
        }
    };

    public int ShowMyModalDialog()  //should be called from non-UI thread
    {
        pressedButtonID = MY_BUTTON_INVALID_ID;
        runOnUiThread(mMyDialog);
        try
        {
            dialogSemaphore.acquire();
        }
        catch (InterruptedException e)
        {
        }
        return pressedButtonID;
    }
Run Code Online (Sandbox Code Playgroud)


fif*_*fth 5

最后,我得出了一个非常简单的解决方案。

熟悉Win32编程的人可能知道如何实现模式对话框。通常,当出现模式对话框时,它将运行嵌套的消息循环(通过GetMessage / PostMessage)。因此,我尝试以这种传统方式实现自己的模式对话框。

最初,android没有提供可插入ui线程消息循环的接口,或者我没有找到接口。当我查看源代码Looper.loop()时,发现它正是我想要的。但是,MessageQueue / Message仍未提供公共接口。幸运的是,我们在Java中有了反思。基本上,我只是完全复制了Looper.loop()所做的事情,它阻止了工作流并仍然正确处理了事件。我还没有测试过嵌套模式对话框,但是从理论上讲它可以工作。

这是我的源代码,

public class ModalDialog {

private boolean mChoice = false;        
private boolean mQuitModal = false;     

private Method mMsgQueueNextMethod = null;
private Field mMsgTargetFiled = null;

public ModalDialog() {
}

public void showAlertDialog(Context context, String info) {
    if (!prepareModal()) {
        return;
    }

    // build alert dialog
    AlertDialog.Builder builder = new AlertDialog.Builder(context);
    builder.setMessage(info);
    builder.setCancelable(false);
    builder.setPositiveButton("Yes", new DialogInterface.OnClickListener() {
        public void onClick(DialogInterface dialog, int id) {
            ModalDialog.this.mQuitModal = true;
            dialog.dismiss();
        }
    });

    AlertDialog alert = builder.create();
    alert.show();

    // run in modal mode
    doModal();
}

public boolean showConfirmDialog(Context context, String info) {
    if (!prepareModal()) {
        return false;
    }

    // reset choice
    mChoice = false;

    AlertDialog.Builder builder = new AlertDialog.Builder(context);
    builder.setMessage(info);
    builder.setCancelable(false);
    builder.setPositiveButton("Yes", new DialogInterface.OnClickListener() {
        public void onClick(DialogInterface dialog, int id) {
            ModalDialog.this.mQuitModal = true;
            ModalDialog.this.mChoice = true;
            dialog.dismiss();
        }
    });

    builder.setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
        public void onClick(DialogInterface dialog, int id) {
            ModalDialog.this.mQuitModal = true;
            ModalDialog.this.mChoice = false;
            dialog.cancel();
        }
    });

    AlertDialog alert = builder.create();
    alert.show();

    doModal();
    return mChoice;
}

private boolean prepareModal() {
    Class<?> clsMsgQueue = null;
    Class<?> clsMessage = null;

    try {
        clsMsgQueue = Class.forName("android.os.MessageQueue");
    } catch (ClassNotFoundException e) {
        e.printStackTrace();
        return false;
    }

    try {
        clsMessage = Class.forName("android.os.Message");
    } catch (ClassNotFoundException e) {
        e.printStackTrace();
        return false;
    }

    try {
        mMsgQueueNextMethod = clsMsgQueue.getDeclaredMethod("next", new Class[]{});
    } catch (SecurityException e) {
        e.printStackTrace();
        return false;
    } catch (NoSuchMethodException e) {
        e.printStackTrace();
        return false;
    }

    mMsgQueueNextMethod.setAccessible(true);

    try {
        mMsgTargetFiled = clsMessage.getDeclaredField("target");
    } catch (SecurityException e) {
        e.printStackTrace();
        return false;
    } catch (NoSuchFieldException e) {
        e.printStackTrace();
        return false;
    }

    mMsgTargetFiled.setAccessible(true);
    return true;
}

private void doModal() {
    mQuitModal = false;

    // get message queue associated with main UI thread
    MessageQueue queue = Looper.myQueue();
    while (!mQuitModal) {
        // call queue.next(), might block
        Message msg = null;
        try {
            msg = (Message)mMsgQueueNextMethod.invoke(queue, new Object[]{});
        } catch (IllegalArgumentException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }

        if (null != msg) {
            Handler target = null;
            try {
                target = (Handler)mMsgTargetFiled.get(msg);
            } catch (IllegalArgumentException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }

            if (target == null) {
                // No target is a magic identifier for the quit message.
                mQuitModal = true;
            }

            target.dispatchMessage(msg);
            msg.recycle();
        }
    }
}
}
Run Code Online (Sandbox Code Playgroud)

希望这会有所帮助。

  • 不要这样做。该代码使用私有API。显然,它使用私有API。任何这样做的人都可以期望他们的应用程序崩溃。 (17认同)
  • 这只是向您显示android框架的设计不佳。您怎能没有一种简单的模态对话框机制? (8认同)
  • 这段代码很尴尬。在他们的头脑中没有人应该使用这些技术。 (2认同)
  • 即使这是不好的编程,我还是只为概念证明而投票。 (2认同)

Per*_*man 5

这对我有用:创建一个Activity作为对话框.然后,

  1. 将此添加到活动的清单中:

    机器人:主题= "@安卓风格/ Theme.Dialog"

  2. 将此添加到您的活动的onCreate

    setFinishOnTouchOutside(false);

  3. 在您的活动中覆盖onBackPressed:

    @Override public void onBackPressed(){//阻止"退回"离开此活动}

第一个给对话框看的活动.后两者使其行为类似于模态对话框.


xan*_*ndy 1

这并不难。

假设您的所有者活动上有一个标志(名为waiting_for_result),每当您的活动恢复时:

public void onResume(){
    if (waiting_for_result) {
        // Start the dialog Activity
    }
}
Run Code Online (Sandbox Code Playgroud)

这保证了所有者活动,除非模式对话框被关闭,否则每当它尝试获得焦点时都会传递到模式对话框活动。