Android: How to get a modal dialog or similar modal behavior?

后端 未结 11 1908
暗喜
暗喜 2020-12-01 05:17

These days I\'m working on simulating modal dialog in Android. I\'ve googled a lot, there\'s much discussions but sadly there\'s not much options to get it modal. Here\'s so

相关标签:
11条回答
  • 2020-12-01 05:26

    It's not difficult.

    Assume you have a flag on your owner activity (named waiting_for_result), whenever your activity is resumed:

    public void onResume(){
        if (waiting_for_result) {
            // Start the dialog Activity
        }
    }
    

    This guaranteed the owner activity, unless the modal dialog is dismissed, whenever it try to get focus will pass to the modal dialog activity.

    0 讨论(0)
  • 2020-12-01 05:26

    I am not sure if this is 100% modal, as you can click on some other component to close the dialog box, but I got confused with the loops constructs and so I offer this as another possibility. It worked nicely for me, so I would like to share the idea. You can create and open the dialog box in one method and then close it in the callback method and the program will wait for the dialog reply before executing the callback method. If you then run the rest of the callback method in a new thread, the dialog box will also close first, before the rest of the code is executed. The only thing you need to do is to have a global dialog box variable, so that different methods can acccess it. So something like the following can work:

    public class MyActivity extends ...
    {
        /** Global dialog reference */
        private AlertDialog okDialog;
    
        /** Show the dialog box */
        public void showDialog(View view) 
        {
            // prepare the alert box
            AlertDialog.Builder alertBox = new AlertDialog.Builder(...);
    
            ...
    
            // set a negative/no button and create a listener
            alertBox.setNegativeButton("No", new DialogInterface.OnClickListener() {
                // do something when the button is clicked
                public void onClick(DialogInterface arg0, int arg1) {
                    //no reply or do nothing;
                }
            });
    
            // set a positive/yes button and create a listener
            alertBox.setPositiveButton("Yes", new DialogInterface.OnClickListener() {
                // do something when the button is clicked
                public void onClick(DialogInterface arg0, int arg1) {
                    callbackMethod(params);
                }
            });
    
            //show the dialog
            okDialog = alertBox.create();
            okDialog.show();
        }
    
    
        /** The yes reply method */
        private void callbackMethod(params)
        {
            //first statement closes the dialog box
            okDialog.dismiss();
    
            //the other statements run in a new thread
            new Thread() {
                public void run() {
                    try {
                        //statements or even a runOnUiThread
                    }
                    catch (Exception ex) {
                        ...
                    }
                }
            }.start();
        }
    }
    
    0 讨论(0)
  • 2020-12-01 05:30

    I got a modal Dialog while using:

    setCancelable(false);
    

    on the DialogFragment (not on the DialogBuilder).

    0 讨论(0)
  • 2020-12-01 05:31

    Finally I ended up with a really straight and simple solution.

    People who's familiar with Win32 programming possibly knows how to implement a modal dialog. Generally it runs a nested message loop (by GetMessage/PostMessage) when there is a modal dialog up. So, I tried to implement my own modal dialog in this traditional way.

    At the first, android didn't provide interfaces to inject into ui thread message loop, or I didn't find one. When I looked into source, Looper.loop(), I found it's exactly what I wanted. But still, MessageQueue/Message haven't provided public interfaces. Fortunately, we have reflection in java. Basically, I just copied exactly what Looper.loop() did, it blocked workflow and still properly handled events. I haven't tested nested modal dialog, but theoretically it would work.

    Here's my source code,

    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();
            }
        }
    }
    }
    

    Hopefully this would help.

    0 讨论(0)
  • 2020-12-01 05:33

    Developers of Android and iOS decided that they are powerful and smart enough to reject Modal Dialog conception (that was on market for many-many years already and didn't bother anyone before), unfortunately for us.

    Here is my solution, it works great:

        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;
        }
    
    0 讨论(0)
  • 2020-12-01 05:35

    I have a similar solution like fifth, but its a little bit simpler and doesn't need reflection. My thinking was, why not use an exception to exit the looper. So my custom looper reads as follows:

    1) The exception that is thrown:

    final class KillException extends RuntimeException {
    }
    

    2) The custom looper:

    public final class KillLooper implements Runnable {
        private final static KillLooper DEFAULT = new KillLooper();
    
        private KillLooper() {
        }
    
        public static void loop() {
            try {
                Looper.loop();
            } catch (KillException x) {
                /* */
            }
        }
    
        public static void quit(View v) {
            v.post(KillLooper.DEFAULT);
        }
    
        public void run() {
            throw new KillException();
        }
    
    }
    

    The use of the custom looper is quite simple. Suppose you have a dialog foo, then simply do the following where you want to call the dialog foo modally:

    a) When calling into foo:

    foo.show();
    KillLooper.loop();
    

    Inside the dialog foo, when you want to exit, you simply call the quit method of the custom looper. This looks as follows:

    b) When exiting from foo:

    dismiss();
    KillLooper.quit(getContentView());
    

    I have recently seen some problems with 5.1.1 Android, do not call a modal dialog from main menu, instead post an event that calls the modal dialog. Without posting the main menu will stall, and I have seen Looper::pollInner() SIGSEGVs in my app.

    0 讨论(0)
提交回复
热议问题