Android: Как получить модальное диалоговое окно или подобное модальное поведение? - PullRequest
53 голосов
/ 25 мая 2011

В эти дни я работаю над симуляцией модального диалога в Android.Я много гуглил, есть много дискуссий, но, к сожалению, не так много вариантов, чтобы сделать это модальным.Вот некоторый фон,
Диалоги, Модальные Диалоги и Блокировка
Диалоги / AlertDialogs: Как "заблокировать выполнение", когда диалог открыт (стиль .NET)

Нет прямого способа получить модальное поведение, тогда я нашел 3 возможных решения,
1. Используйте занятие на тему диалога, как это thread сказал, но я все еще могу 'не заставлять основную деятельность действительно ждать возвращения диалоговой активности.Основная активность превратилась в состояние остановки и затем была перезапущена.
2. Создайте один рабочий поток и используйте синхронизацию потоков.Тем не менее, это огромная работа по рефакторингу для моего приложения, теперь у меня есть одно основное действие и служба в основном потоке пользовательского интерфейса.
3. Возьмите на себя обработку событий в цикле, когда есть модальное диалоговое окно, и выход из циклакогда диалог закрывается.На самом деле это способ создать настоящий модальный диалог, как это делается в Windows.Я до сих пор не прототипировал этот способ.

Я все еще хотел бы смоделировать его с помощью диалоговой темы,
1. Запустите диалоговое действие с помощью startActivityForResult ()
2. получите результатfrom 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;
    }
}
}

Вызывающая функция startAlertDialog заблокирует выполнение и ожидает возвращаемый результат.Но startAlertDialog вернулось немедленно, и основное действие перешло в состояние STOP, пока DialogActivity работала.

Итак, вопрос в том, как заставить основную деятельность действительно ждать результата?
Спасибо.

Ответы [ 11 ]

65 голосов
/ 25 февраля 2013

Я получил модальное диалоговое окно при использовании:

setCancelable(false);

на DialogFragment (не на DialogBuilder).

13 голосов
/ 25 мая 2011

Это не так, как вы планировали.Во-первых, вам не разрешено блокировать поток пользовательского интерфейса.Ваша заявка будет прекращена.Во-вторых, необходимо обработать методы жизненного цикла, которые вызываются, когда другое действие запускается с startActivity (ваша исходная активность будет приостановлена, пока другое действие выполняется).В-третьих, вы, вероятно, могли бы как-то взломать его, используя startAlertDialog() не из потока пользовательского интерфейса, с синхронизацией потоков (например, 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();

Затем обработайте два методаположительный или отрицательный отзыв соответственно (т. е. выполнение какой-либо операции или завершение действия или что-либо еще, что имеет смысл).

7 голосов
/ 14 июля 2012

Разработчики 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;
    }
5 голосов
/ 06 февраля 2013

Это работает для меня: создайте Activity как ваш диалог. Тогда,

  1. Добавьте это в свой манифест для действия:

    Android: тема = "@ андроид: стиль / Theme.Dialog"

  2. Добавьте это в onCreate своей деятельности

    setFinishOnTouchOutside (false);

  3. Переопределить onBackНажмите в своей активности:

    @ Override public void onBackPressed () { // предотвращаем «возврат» из этого действия }

Первый дает деятельности вид диалога. Последние два заставляют его вести себя как модальный диалог.

4 голосов
/ 01 июня 2011

Наконец-то я получил действительно прямое и простое решение.

Люди, которые знакомы с программированием на Win32, возможно, знают, как реализовать модальное диалоговое окно.Обычно он запускает вложенный цикл сообщений (с помощью GetMessage / PostMessage), когда появляется модальное диалоговое окно.Итак, я попытался реализовать свой собственный модальный диалог таким традиционным способом.

Сначала Android не предоставлял интерфейсы для внедрения в цикл сообщений пользовательского интерфейса, или я не нашел ни одного.Когда я посмотрел на источник, 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();
        }
    }
}
}

Надеюсь, это поможет.

1 голос
/ 27 января 2016

Я не уверен, является ли это 100% модальным, поскольку вы можете щелкнуть по другому компоненту, чтобы закрыть диалоговое окно, но я запутался с конструкциями циклов, и поэтому я предлагаю это как еще одну возможность.Это хорошо сработало для меня, поэтому я хотел бы поделиться этой идеей.Вы можете создать и открыть диалоговое окно в одном методе, а затем закрыть его в методе обратного вызова, и программа будет ожидать ответа диалога, прежде чем выполнять метод обратного вызова.Если вы затем запустите оставшуюся часть метода обратного вызова в новом потоке, диалоговое окно также закроется в первую очередь, прежде чем будет выполнен остальной код.Единственное, что вам нужно сделать, это иметь глобальную переменную диалогового окна, чтобы разные методы могли получить к ней доступ.Так что может работать что-то вроде следующего:

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();
    }
}
1 голос
/ 01 июня 2015

У меня есть похожее решение, например пятое , но оно немного проще и не требует отражения.Я думал, почему бы не использовать исключение для выхода из петлителя.Итак, мой пользовательский петлитель выглядит следующим образом:

1) Исключение, которое выдается:

final class KillException extends RuntimeException {
}

2) Пользовательский петлитель:

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

}

ИспользованиеПользовательский петлитель довольно прост.Предположим, у вас есть диалоговое окно foo, затем просто сделайте следующее, где вы хотите вызвать диалоговое окно foo модально:

a) При вызове foo:

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

Внутри диалогового окна foo, когдаЕсли вы хотите выйти, вы просто вызываете метод quit пользовательского петлителя.Это выглядит следующим образом:

b) При выходе из foo:

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

Недавно я видел некоторые проблемы с Android 5.1.1, вместо этого вызывайте модальное диалоговое окно из главного меню.опубликовать событие, которое вызывает модальный диалог.Без публикации главное меню остановится, и я видел Looper :: pollInner () SIGSEGVs в моем приложении.

1 голос
/ 12 сентября 2013

Как отметили hackbod и другие, Android сознательно не предоставляет метод для выполнения вложенных циклов событий. Я понимаю причины этого, но есть определенные ситуации, которые требуют их. В нашем случае у нас есть собственная виртуальная машина, работающая на разных платформах, и мы хотели портировать ее на Android. Внутри есть много мест, где требуется вложенный цикл обработки событий, и не представляется возможным переписать все это только для Android. В любом случае, вот решение (в основном взято из Как я могу сделать неблокирующую обработку событий на Android? , но я добавил время ожидания):

private class IdleHandler implements MessageQueue.IdleHandler
{
    private Looper _looper;
    private int _timeout;
    protected IdleHandler(Looper looper, int timeout)
    {
        _looper = looper;
        _timeout = timeout;
    }

    public boolean queueIdle()
    {
        _uiEventsHandler = new Handler(_looper);
        if (_timeout > 0)
        {
            _uiEventsHandler.postDelayed(_uiEventsTask, _timeout);
        }
        else
        {
            _uiEventsHandler.post(_uiEventsTask);
        }
        return(false);
    }
};

private boolean _processingEventsf = false;
private Handler _uiEventsHandler = null;

private Runnable _uiEventsTask = new Runnable()
{
    public void run() {
    Looper looper = Looper.myLooper();
    looper.quit();
    _uiEventsHandler.removeCallbacks(this);
    _uiEventsHandler = null;
    }
};

public void processEvents(int timeout)
{
    if (!_processingEventsf)
    {
        Looper looper = Looper.myLooper();
        looper.myQueue().addIdleHandler(new IdleHandler(looper, timeout));
        _processingEventsf = true;
        try
        {
            looper.loop();
        } catch (RuntimeException re)
        {
            // We get an exception when we try to quit the loop.
        }
        _processingEventsf = false;
     }
}
1 голос
/ 25 августа 2012

Одно из решений:

  1. Поместите весь код для каждой выбранной кнопки в слушатель каждой кнопки.
  2. alert.show(); должна быть последней строкой кода в функции, вызывающей Alert. Любой код после этой строки не будет ожидать закрытия оповещения, но будет выполнен немедленно.

Надеюсь, что Помощь!

1 голос
/ 25 мая 2011

Это не сложно.

Предположим, у вас есть флаг на активность вашего владельца (с именем waiting_for_result), когда ваша деятельность возобновляется:

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

Это гарантирует активность владельца, если модальное диалоговое окно не закрыто,всякий раз, когда он пытается сфокусироваться, он переходит к модальному диалогу.

...