tipos - Android: ¿Cómo obtener un diálogo modal o un comportamiento modal similar?
tipos de dialogos en android studio (10)
Como han señalado hackbod y otros, Android deliberadamente no proporciona un método para hacer bucles de eventos anidados. Entiendo las razones para esto, pero hay ciertas situaciones que lo requieren. En nuestro caso, tenemos nuestra propia máquina virtual ejecutándose en varias plataformas y queríamos conectarla a Android. Internamente hay muchos lugares donde se requiere un bucle de eventos anidados, y no es realmente posible reescribir todo para Android. De todos modos, aquí hay una solución (básicamente tomada de ¿Cómo puedo hacer un proceso de eventos sin bloqueo en Android?, Pero he agregado un tiempo de espera):
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;
}
}
Estos días estoy trabajando en la simulación de diálogo modal en Android. He buscado mucho en Google, hay muchas discusiones, pero lamentablemente no hay muchas opciones para hacerlo modal. Aquí hay algunos antecedentes,
Diálogos, Diálogos modales y Blockin
Diálogos / AlertDialogs: cómo "bloquear la ejecución" mientras el diálogo está activo (estilo .NET)
No hay una forma directa de obtener un comportamiento modal, entonces se me ocurrió 3 posibles soluciones,
1. Use una actividad con un tema de diálogo, como este thread , pero todavía no puedo hacer que la actividad principal realmente espere el retorno de la actividad del diálogo. La actividad principal se activó para detener el estado y luego se reinició.
2. Cree un hilo de trabajador y use la sincronización de hilo. Sin embargo, es un gran trabajo de refactorización para mi aplicación, ahora tengo una sola actividad principal y un servicio tanto en el hilo principal de la interfaz de usuario.
3. Asumir el manejo de eventos dentro de un bucle cuando hay un diálogo modal arriba, y salir del bucle cuando el diálogo se cierra. En realidad, es la forma de construir un diálogo modal real como lo que hace exactamente en Windows. Todavía no tengo un prototipo de esta manera.
Todavía me gustaría simularlo con una actividad con temas de diálogo,
1. iniciar dialog-activity por startActivityForResult ()
2. obtener el resultado de onActivityResult ()
Aquí hay alguna fuente
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;
}
}
}
La persona que llama a startAlertDialog bloqueará la ejecución y esperará un resultado devuelto. Pero startAlertDialog volvió inmediatamente, por supuesto, y la actividad principal pasó al estado STOP mientras DialogActivity estaba activa.
Entonces la pregunta es, ¿cómo hacer que la actividad principal realmente espere el resultado?
Gracias.
Esto funciona para mí: crea una actividad como tu diálogo. Entonces,
Agregue esto a su manifiesto para la actividad:
android: theme = "@ android: style / Theme.Dialog"
Agregar esto a onCreate de tu actividad
setFinishOnTouchOutside (falso);
Anular OnBackPressed en tu actividad:
@Override public void onBackPressed () {// evita que "back" deje esta actividad}
El primero le da a la actividad el aspecto del diálogo. Los dos últimos hacen que se comporte como un diálogo modal.
Finalmente terminé con una solución realmente directa y simple.
Las personas que están familiarizadas con la programación de Win32 posiblemente saben cómo implementar un diálogo modal. En general, ejecuta un bucle de mensajes anidados (por GetMessage / PostMessage) cuando hay un diálogo modal arriba. Entonces, traté de implementar mi propio diálogo modal de esta manera tradicional.
Al principio, Android no proporcionaba interfaces para inyectar en el bucle de mensajes de hilos ui, o no encontré ninguno. Cuando busqué en source, Looper.loop (), encontré que es exactamente lo que quería. Pero aún así, MessageQueue / Message no ha proporcionado interfaces públicas. Afortunadamente, tenemos reflejo en Java. Básicamente, acabo de copiar exactamente lo que hizo Looper.loop (), bloqueó el flujo de trabajo y aún manejó adecuadamente los eventos. No he probado el diálogo modal anidado, pero teóricamente funcionaría.
Aquí está mi código fuente,
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();
}
}
}
}
Espero que esto ayude.
Los desarrolladores de Android e iOS decidieron que son lo suficientemente poderosos e inteligentes como para rechazar la concepción de Modal Dialog (que ya llevaba muchos años en el mercado y no molestaba a nadie antes), desafortunadamente para nosotros.
Aquí está mi solución, funciona genial:
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;
}
No es dificil.
Supongamos que tiene una bandera en su actividad de propietario (llamada waiting_for_result
), siempre que se reanude su actividad:
public void onResume(){
if (waiting_for_result) {
// Start the dialog Activity
}
}
Esto garantizaba la actividad del propietario, a menos que se desestime el diálogo modal, cada vez que intenta obtener el foco pasará a la actividad de diálogo modal.
No es posible de la manera que planeaste. Primero, no está permitido bloquear el hilo de UI. Su aplicación será cancelada. Segundo, necesita manejar los métodos del ciclo de vida que se llaman cuando se inicia otra actividad con startActivity
(su actividad original se pausará mientras se está ejecutando la otra actividad). En tercer lugar, probablemente podría de alguna manera piratearlo utilizando startAlertDialog()
no desde el subproceso UI, con sincronización de subprocesos (como Object.wait()
) y algunos AlertDialog
. Sin embargo, te recomiendo encarecidamente que no hagas esto. Es feo, seguramente se romperá y simplemente no es la forma en que las cosas están destinadas a funcionar.
Rediseñe su enfoque para capturar la naturaleza asíncrona de estos eventos. Si desea, por ejemplo, un cuadro de diálogo que le solicite al usuario una descisión (como aceptar el ToS o no) y realizar acciones especiales basadas en esa decisión, cree un diálogo como este:
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();
Luego, tenga dos métodos que manejen la retroalimentación positiva o negativa en consecuencia (es decir, proceda con alguna operación o finalice la actividad o lo que sea que tenga sentido).
No estoy seguro de si esto es 100% modal, ya que puede hacer clic en algún otro componente para cerrar el cuadro de diálogo, pero me confundí con los constructos de bucles, por lo que ofrezco esto como otra posibilidad. Funcionó muy bien para mí, así que me gustaría compartir la idea. Puede crear y abrir el cuadro de diálogo en un método y luego cerrarlo en el método de devolución de llamada y el programa esperará la respuesta del diálogo antes de ejecutar el método de devolución de llamada. Si luego ejecuta el resto del método de devolución de llamada en un nuevo hilo, el cuadro de diálogo también se cerrará primero, antes de que se ejecute el resto del código. Lo único que debe hacer es tener una variable de cuadro de diálogo global, para que diferentes métodos puedan acceder a ella. Entonces algo como lo siguiente puede funcionar:
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();
}
}
Obtuve un cuadro de diálogo modal al usar:
setCancelable(false);
en DialogFragment (no en DialogBuilder).
Tengo una solución similar a la , pero es un poco más simple y no necesita reflexión. Mi pensamiento era, ¿por qué no usar una excepción para salir del looper? Entonces mi looper personalizado dice lo siguiente:
1) La excepción que se arroja:
final class KillException extends RuntimeException {
}
2) El looper personalizado:
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();
}
}
El uso del looper personalizado es bastante simple. Supongamos que tiene un diálogo foo, luego simplemente haga lo siguiente donde quiera llamar al diálogo foo modalmente:
a) Cuando llame a foo:
foo.show();
KillLooper.loop();
Dentro del cuadro de diálogo foo, cuando quiere salir, simplemente llama al método de abandono del looper personalizado. Esto se ve de la siguiente manera:
b) Al salir de foo:
dismiss();
KillLooper.quit(getContentView());
Recientemente he visto algunos problemas con 5.1.1 Android, no invoco un diálogo modal desde el menú principal, sino que publico un evento que llama al diálogo modal. Sin publicar, el menú principal se detendrá, y he visto los SIGSEGVs de Looper :: pollInner () en mi aplicación.
Una solución es:
- Ponga todo el código para cada botón seleccionado en el oyente de cada botón.
-
alert.show();
debe ser la última línea de código en la función que llama a Alert. Cualquier código después de esta línea no esperará para cerrar la alerta, sino que se ejecutará inmediatamente.
Espero ayuda!