Диалоги / AlertDialogs: как "блокировать выполнение" во время диалога (.NET-стиль)
исходящий от .Чистой окружающей среды им теперь хотите понять, как работать диалоговые окна в Android.
в .NET, при вызове MessageBox.Шоу.(..) это создает и показывает всплывающее диалоговое окно. В вызове, который я могу указать, какие кнопки должны быть доступны во всплывающем окне, например:
DialogResult myDialogResult = MessageBox.Show("My text here", "My caption here", MessageBoxButtons.YesNoCancel);
как вы можете видеть, вызов Show возвращает DialogResult при нажатии кнопки во всплывающем окне, сообщая мне, какая кнопка была нажата. Обратите внимание, что в .NET, выполнение останавливается на линии, где вызов показать(...) сделано, поэтому он может вернуть значение при нажатии кнопки.
Если я в приведенном выше примере нажму "нет", то myDialogResult будет равен
myDialogResult == DialogResult.No
Так как я нахожу .NET-способ использования / создания всплывающих окон очень простым и интуитивно понятным, я хотел бы, чтобы этот способ создания всплывающих окон в Android тоже.
Итак, вопрос в том, знает ли кто-нибудь, как "остановить выполнение", как с помощью MessageBox.Показывать, а затем возвращать значение всякий раз, когда кнопка нажата (и диалог уходит)?
в отношении
EDIT 1: Чтобы быть немного более ясным:
мне нужно для выполнения, чтобы остановить и ждать, пока пользователь не выбрал кнопку, чтобы нажать в всплывающем окне. Код, который следует за вызовом, чтобы показать диалоговое окно, зависит от того, какая кнопка нажата в диалоговом окне.
вот почему я не могу использовать то, что предлагают Эрих и Алекс, Так как пишу код в onClick-методах, как предлагается ниже, не будет работать. Причина в том, что я не могу продолжать "нормальное исполнение". Позвольте мне взять пример:
позвольте мне взять пример:
int nextStep = 0; // this variable will not be reached from within the onClick-methods
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setMessage("Hello!")
.setPositiveButton("Ok", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
nextStep = 1; // *** COMPILER ERROR!! ***
}
})
.setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
nextStep = 2; // *** COMPILER ERROR!! ***
}
})
.create().show();
if (nextStep == 1)
{
// then do some damage
}
else if (nextStep == 2
// dont do damage
Если бы я хотел, чтобы выполнение зависело от выбора во всплывающем окне, мне как-то пришлось бы сделать все переменные в "нормальном выполнении" (в этом случае nextStep) доступно в onClick-методах, и это звучит как ад мне.
EDIT 2:
другим очевидным примером может быть всплывающее окно с запросом "хочешь продолжить" с параметрами "да" и "нет".
если пользователь нажимает "да", весь метод должен быть прерван, в противном случае следует продолжить выполнение. Как вы решаете это красиво?
в отношении
18 ответов:
Ted, вы не хотите этого делать, на самом деле :) самая большая причина заключается в том, что если вы блокируете поток пользовательского интерфейса во время отображения диалога, вы заблокируете поток, который отвечает за рисование и обработку событий вашего диалога. Это означает, что ваш диалог не будет отвечать на запросы. Вы также вызовете ANRs, если пользователю потребуется более нескольких секунд, чтобы щелкнуть диалоговое окно.
Эрих-это именно то, что вам нужно. Я знаю, что это не то, что вы хочу, но это не важно. Мы разработали Android, чтобы разработчики не писали синхронные диалоги, поэтому у вас действительно нет большого выбора.
в Android структура отличается от .NET:
AlertDialog.Builder builder = new AlertDialog.Builder(this); builder.setMessage("Hello!") .setPositiveButton("Ok", new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int id) { // Handle Ok } }) .setNegativeButton("Cancel", new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int id) { // Handle Cancel } }) .create();
будет Вам диалоговое окно с двумя кнопками и обрабатывать нажатия кнопок с ответами. Возможно, вы сможете написать некоторый код, чтобы сделать синтаксис более похожим на .NET, но жизненный цикл диалога довольно тесно переплетен с
Activity
, Так что в конце концов, это может быть больше проблем, чем оно стоит. Дополнительные ссылки на диалог здесь.
упрощенная версия ответа Даниила выше. Эта функция возвращает да или нет от пользователя в диалоговом окне предупреждения, но может быть легко изменен, чтобы сделать другой вход.
private boolean mResult; public boolean getYesNoWithExecutionStop(String title, String message, Context context) { // make a handler that throws a runtime exception when a message is received final Handler handler = new Handler() { @Override public void handleMessage(Message mesg) { throw new RuntimeException(); } }; // make a text input dialog and show it AlertDialog.Builder alert = new AlertDialog.Builder(context); alert.setTitle(title); alert.setMessage(message); alert.setPositiveButton("Yes", new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int whichButton) { mResult = true; handler.sendMessage(handler.obtainMessage()); } }); alert.setNegativeButton("No", new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int whichButton) { mResult = false; handler.sendMessage(handler.obtainMessage()); } }); alert.show(); // loop till a runtime exception is triggered. try { Looper.loop(); } catch(RuntimeException e2) {} return mResult; }
В Android диалоги асинхронны, поэтому вам придется структурировать свой код немного по-другому.
Итак, в C# ваша логика выполняла что-то вроде этого в псевдокоде:
void doSomeStuff() { int result = showDialog("Pick Yes or No"); if (result == YES) { //do stuff for yes } else if (result == NO) { //do stuff for no } //finish off here }
для Android это должно быть менее аккуратно. Подумайте об этом вот так. Вы будете иметь
OnClickListener
такой:public void onClick(DialogInterface dialog, int whichButton) { if (whichButton == BUTTON_POSITIVE) { doOptionYes(); } else if (whichButton == BUTTON_NEGATIVE) { doOptionNo(); } }
, который затем поддерживается следующими методами:
void doOptionYes() { //do stuff for yes endThings(); } void doOptionNo() { //do stuff for no endThings(); } void endThings() { //clean up here }
так что был один метод теперь четыре. Это может показаться не так аккуратно, но вот как это работает, я боюсь.
PasswordDialog dlg = new PasswordDialog(this); if(dlg.showDialog() == DialogResult.OK) { //blabla, anything your self } public class PasswordDialog extends Dialog { int dialogResult; Handler mHandler ; public PasswordDialog(Activity context, String mailName, boolean retry) { super(context); setOwnerActivity(context); onCreate(); TextView promptLbl = (TextView) findViewById(R.id.promptLbl); promptLbl.setText("Input password/n" + mailName); } public int getDialogResult() { return dialogResult; } public void setDialogResult(int dialogResult) { this.dialogResult = dialogResult; } /** Called when the activity is first created. */ public void onCreate() { setContentView(R.layout.password_dialog); findViewById(R.id.cancelBtn).setOnClickListener(new android.view.View.OnClickListener() { @Override public void onClick(View paramView) { endDialog(DialogResult.CANCEL); } }); findViewById(R.id.okBtn).setOnClickListener(new android.view.View.OnClickListener() { @Override public void onClick(View paramView) { endDialog(DialogResult.OK); } }); } public void endDialog(int result) { dismiss(); setDialogResult(result); Message m = mHandler.obtainMessage(); mHandler.sendMessage(m); } public int showDialog() { mHandler = new Handler() { @Override public void handleMessage(Message mesg) { // process incoming messages here //super.handleMessage(msg); throw new RuntimeException(); } }; super.show(); try { Looper.getMainLooper().loop(); } catch(RuntimeException e2) { } return dialogResult; } }
в попытке оптимизировать память и производительность диалоги в Android являются асинхронными (они также управляются по этой причине). Выходя из мира Windows, вы привыкли к модальным диалогам. Диалоги Android являются модальными, но больше похожи на немодальные, когда дело доходит до выполнения. Выполнение не останавливается после отображения диалогового окна.
лучшее описание диалогов в Android я видел в "Pro Android" http://www.apress.com/book/view/1430215968
Это не идеальное объяснение, но оно должно помочь вам понять различия между диалоговыми окнами в Windows и Android. В Windows вы хотите сделать A, задать вопрос с диалогом, а затем сделать B или C. В android design A со всем кодом, который вам нужен для B и C в onClick() OnClickListener(s) для диалога. Затем выполните A и запустите диалоговое окно. С тобой покончено! Когда пользователь нажимает кнопку кнопка B или C будет выполнена.
Windows ------- A code launch dialog user picks B or C B or C code done! Android ------- OnClick for B code (does not get executed yet) OnClick for C code (does not get executed yet) A code launch dialog done! user picks B or C
разработчики Android и iOS решили, что они достаточно сильны и умны, чтобы отказаться от концепции модального диалога (которая была на рынке уже много-много лет и никого раньше не беспокоила), к сожалению для нас. Я считаю, что для Android есть работа - поскольку вы можете показать диалог из потока без пользовательского интерфейса с помощью класса Runnable, должен быть способ подождать в этом потоке (без пользовательского интерфейса), пока диалог не будет завершен.
изменить: Вот мое решение, оно работает отлично:
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; }
Тед, как вы, вероятно, узнали, вы, к сожалению, не можете сделать это на Android. Диалоги являются модальными, но асинхронными, и это определенно нарушит последовательность, которую вы пытаетесь установить, как это было бы сделано на .NET (или Windows, если на то пошло). Вам придется крутить свой код вокруг и сломать некоторую логику, которая была бы очень легко следовать на основе вашего примера.
еще один очень простой пример, чтобы сохранить данные в файл, только чтобы узнать, что файл уже есть там и просят переписать его или нет. Вместо отображения диалогового окна и наличия оператора if для действия на результат (Да/Нет), вам придется использовать обратные вызовы (называемые слушателями в Java) и разделить логику на несколько функций.
в Windows, когда отображается диалоговое окно, сообщение насос продолжается в фоновом режиме (только текущее сообщение обрабатывается на удержание), и это работает просто отлично. Это позволяет пользователям перемещать ваше приложение и перекрашивать его во время отображения диалоговое окно, например. WinMo поддерживает синхронные модальные диалоги, так же как и BlackBerry, но только не Android.
самое чистое и простое решение-использовать свой собственный интерфейс прослушивателя, чтобы, когда пользователь нажимает на кнопку ok, ваш слушатель вызывается с возвращаемым значением. Этот метод не делает ничего необычного или сложного и уважает принципы android.
определить свой интерфейс слушателя следующим образом:
public interface EditListener /* Used to get an integer return value from a dialog * */ { void returnValue(int value); }
для моего приложения я создал класс EditValue, который использует AlertDialog и который я вызываю всякий раз, когда хочу отредактировать целое значение. Обратите внимание, как EditListener интерфейс передается в качестве аргумента к этому коду. Когда пользователь нажимает на кнопку OK, значение будет возвращено в ваш код вызова с помощью метода EditListener:
public final class EditValue /* Used to edit a value using an alert dialog * The edited value is returned via the returnValue method of the supplied EditListener interface * Could be modified with different constructors to edit double/float etc */ { public EditValue(final Activity parent, int value, String title, String message, final EditListener editListener) {AlertDialog.Builder alert= new AlertDialog.Builder(parent); if(title==null) title= message; else if(message==null) message= title; if(title!=null) alert.setTitle(title); if(message!=null) alert.setMessage(message); // Set an EditText view to get user input final EditText input = new EditText(parent); input.setText(String.valueOf(value)); input.setInputType(InputType.TYPE_CLASS_NUMBER); alert.setView(input); alert.setPositiveButton("OK",new DialogInterface.OnClickListener() {public void onClick(DialogInterface dialog, int which) {try {int newValue= Integer.valueOf(input.getText().toString()); editListener.returnValue(newValue); dialog.dismiss(); }catch(NumberFormatException err) { } } }); alert.setNegativeButton("Cancel", new DialogInterface.OnClickListener() {public void onClick(DialogInterface dialog, int which) {dialog.dismiss(); } }); alert.show(); } }
наконец, когда вы используете EditValue вам нужно объявить свой EditListener и теперь вы можете получить доступ к возвращаемому значению и делать то, что вы хотите сделать:
new EditValue(main,AnchorManager.anchorageLimit, main.res.getString(R.string.config_anchorage_limit),null, new EditListener() {public void returnValue(int value) {AnchorManager.anchorageLimit= value;} } );
переписывание:
есть радикальные различия между мобильной и настольной среды, а также между тем, как приложения были разработаны несколько лет назад и сегодня:
a) мобильные устройства должны экономить энергию. Часть стоимости, которую они предлагают. Так что вам нужно экономить ресурсы. Потоки-это дорогостоящий ресурс. Остановка прогресса потока является недопустимой тратой этого ресурса.
b) В настоящее время пользователь гораздо больше требующий. Чтобы помочь им, мы считаем, что он заслуживает того, чтобы иметь полный рабочий процессор и наименьшие возможные затраты энергии. Его приложение не только на устройстве, есть неизвестное количество других приложений, работающих в то же время, и ваше приложение не обязательно самое срочное.
C) блокировки системного уровня не являются опцией: мобильное устройство работает с рядом событий и услуг в фоновом режиме, и это не подходит для любого из них может быть заблокировано приложением.
подумайте о том, что пользователь получает телефонный звонок, пока работает ваша "системная блокировка"...
исходя из вышеизложенных фактов, ответом на предложенный вопрос являются:
- есть жизнеспособных способ построить диалог, который блокирует основной поток до получения ответа от пользователя?
нет. Обходные пути делают пользовательский опыт хуже, и он может сделать ошибку, обвиняя саму систему. Это несправедливо и наказывает платформу и всех ее разработчиков.
- есть ли способ заблокировать всю систему с помощью диалога?
нет. Это строго запрещено на платформе. Ни одно приложение не может вмешиваться в работу системы или других приложений.
- мне нужно рефакторинг моего приложения, или переосмыслить свой способ программирования, чтобы удовлетворить меня на архитектуре мобильной системы Android.
да. В том числе и в этом аспекте.
вы устанавливаете прослушиватели onclick для вас кнопки. dissmis диалог и сделать свое действие. Не нужно ничего останавливать
protected Dialog onCreateDialog(int id) { return new AlertDialog.Builder(this).setTitle(R.string.no_connection).setIcon(android.R.drawable.ic_dialog_alert).setView(textEntryView).setPositiveButton(R.string.exit, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int whichButton) { // Here goes what you want to do } }) }
для вызова-ex-showDialog(DIALOG_ERROR_PREF);
более http://developer.android.com/guide/topics/ui/dialogs.html
просто ответить на ваш вопрос...кстати sry, что я на 9 месяцев опоздал: D...есть "обходной путь" 4 такого рода проблем. то есть
new AlertDialog.Builder(some_class.this).setTitle("bla").setMessage("bla bla").show(); wait();
просто добавить ждать();
и они в OnClickListener снова начинают класс с уведомления() что-то вроде этого
@Override public void onClick(DialogInterface dialog, int item) { Toast.makeText(getApplicationContext(), "test", Toast.LENGTH_LONG).show(); **notify**(); dialog.cancel(); }
тот же обходной путь идет 4 тосты и другие асинхронные вызовы в android
Я новичок в мире Android / Java и был удивлен, узнав здесь (Если я не понимаю, что я читаю), что модальные диалоги не работают. По некоторым очень неясным причинам для меня на данный момент я получил этот эквивалент "ShowMessage" с кнопкой ok, которая работает на моем планшете очень модально.
из моих TDialogs.Ява модуль:
class DialogMes { AlertDialog alertDialog ; private final Message NO_HANDLER = null; public DialogMes(Activity parent,String aTitle, String mes) { alertDialog = new AlertDialog.Builder(parent).create(); alertDialog.setTitle(aTitle); alertDialog.setMessage(mes) ; alertDialog.setButton("OK",NO_HANDLER) ; alertDialog.show() ; } }
вот часть кода:
public class TestDialogsActivity extends Activity implements DlgConfirmEvent { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); Button btShowMessage = (Button) findViewById(R.id.btShowMessage); btShowMessage.setOnClickListener(new View.OnClickListener() { public void onClick(View view) { DialogMes dlgMes = new DialogMes( TestDialogsActivity.this,"Message","life is good") ; } });
Я также реализовал модальный диалог Да / нет после интерфейс подход, предложенный выше JohnnyBeGood, и он работает довольно хорошо тоже.
устранение:
мой ответ не имеет отношения к вопросу, который я неправильно понял. По какой-то причине я интерпретировал M. Romain Guy "вы не хотите этого делать" как нет-нет модальным диалогам. Я читал: "вы не хотите этого делать...такой образ."
Я прошу прощения.
попробуйте это в потоке (не UI-поток):
final CountDownLatch latch = new CountDownLatch(1); handler.post(new Runnable() { @Override public void run() { OnClickListener okListener = new OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { dialog.dismiss(); latch.countDown(); } }; AlertDialog dialog = new AlertDialog.Builder(context).setTitle(title) .setMessage(msg).setPositiveButton("OK", okListener).create(); dialog.show(); } }); try { latch.await(); } catch (InterruptedException e) { e.printStackTrace(); }
UserSelect =null AlertDialog.Builder builder = new Builder(ImonaAndroidApp.LoginScreen); builder.setMessage("you message"); builder.setPositiveButton("OK", new OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { UserSelect = true ; } }); builder.setNegativeButton("Cancel", new OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { UserSelect = false ; } }); // in UI thread builder.show(); // wait until the user select while(UserSelect ==null);
Я использую Xamarin.Андроид (MonoDroid) и у меня есть требования для разработки пользовательского интерфейса блокировки окно подтверждения. Я не собираюсь спорить с клиентом, потому что я вижу причины, почему они хотят, что (подробности здесь), поэтому мне нужно реализовать это. Я попробовал @Daniel и @MindSpiker выше, но они не работали на MonoForAndroid, в тот момент, когда сообщение отправляется между потоками, приложение разбивается. Я предполагаю, что это как-то связано с сопоставлением Xamarin.
I в итоге был создан отдельный поток из потока пользовательского интерфейса, а затем заблокирован и ждет ответа пользователя следующим образом:
// (since the controllers code is shared cross-platforms) protected void RunConfirmAction(Action runnableAction) { if (runnableAction != null) { if (Core.Platform.IsAndroid) { var confirmThread = new Thread(() => runnableAction()); confirmThread.Start(); } else { runnableAction(); } } } // The call to the logout method has now changed like this: RunConfirmAction(Logout); // the implemtation of the MessageBox waiting is like this: public DialogResult MessageBoxShow(string message, string caption, MessageBoxButtons buttons, MessageBoxIcon icon, MessageBoxDefaultButton defaultButton) { if (_CurrentContext != null && _CurrentContext.Screen != null && MainForm.MainActivity != null) { Action<bool> callback = OnConfirmCallBack; _IsCurrentlyInConfirmProcess = true; Action messageBoxDelegate = () => MessageBox.Show(((Activity)MainForm.MainActivity), callback, message, caption, buttons); RunOnMainUiThread(messageBoxDelegate); while (_IsCurrentlyInConfirmProcess) { Thread.Sleep(1000); } } else { LogHandler.LogError("Trying to display a Message box with no activity in the CurrentContext. Message was: " + message); } return _ConfirmBoxResult ? DialogResult.OK : DialogResult.No; } private void OnConfirmCallBack(bool confirmResult) { _ConfirmBoxResult = confirmResult; _IsCurrentlyInConfirmProcess = false; } private bool _ConfirmBoxResult = false; private bool _IsCurrentlyInConfirmProcess = false;
полную информацию об этом можно найти в моем блоге здесь