Android: Comment obtenir un dialog modal ou un comportement modal similaire?

Ces jours-ci, je travaille sur la simulation du dialog modal sous Android. J’ai beaucoup googlé, il y a beaucoup de discussions mais malheureusement, il n’y a pas beaucoup d’options pour le rendre modal. Voici un peu de contexte,
Dialogues, dialogs modaux et blocage
Dialogs / AlertDialogs: Comment “bloquer l’exécution” pendant que la boîte de dialog est active (style .NET)

Il n’ya pas de moyen simple d’obtenir un comportement modal, alors j’ai proposé 3 solutions possibles,
1. Utilisez une activité sur le thème des dialogs, comme ce thread l’a dit, mais je ne peux toujours pas faire en sorte que l’activité principale attende vraiment le retour de l’activité du dialog. L’activité principale s’est arrêtée pour arrêter le statut et a ensuite été redémarrée.
2. Créez un thread de travail et utilisez la synchronisation des threads. Cependant, c’est un travail de refactoring énorme pour mon application, maintenant j’ai une activité principale unique et un service à la fois dans le thread principal de l’interface utilisateur.
3. Prenez en charge la gestion des événements dans une boucle lorsqu’il existe une boîte de dialog modale et quittez la boucle lorsque la boîte de dialog se ferme. En fait, c’est le moyen de créer une véritable boîte de dialog modale, comme dans Windows. Je n’ai toujours pas fait de prototypage de cette façon.

Je voudrais encore le simuler avec une activité sur le thème du dialog,
1. démarrez dialog-activity par startActivityForResult ()
2. obtenir le résultat de onActivityResult ()
Voici une source

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

L’appelant de startAlertDialog bloque l’exécution et attend le résultat renvoyé. Mais startAlertDialog est revenu immédiatement bien sûr et l’activité principale est passée à l’état STOP pendant que DialogActivity était actif.

Donc, la question est de savoir comment faire en sorte que l’activité principale attende vraiment le résultat.
Merci.

J’ai eu un dialog modal en utilisant:

 setCancelable(false); 

sur le DialogFragment (pas sur le DialogBuilder).

Ce n’est pas possible comme vous l’aviez prévu. Tout d’abord, vous n’êtes pas autorisé à bloquer le thread d’interface utilisateur. Votre demande sera résiliée. Deuxièmement, vous devez gérer les méthodes de cycle de vie appelées lorsqu’une autre activité est démarrée avec startActivity (votre activité d’origine sera suspendue pendant que l’autre activité est en cours d’exécution). Troisièmement, vous pourriez probablement le pirater en utilisant startAlertDialog() non le thread d’interface utilisateur, avec la synchronisation des threads (comme Object.wait() ) et certains AlertDialog . Cependant, je vous encourage fortement à ne pas le faire. C’est moche, va certainement rompre et ce n’est pas la façon dont les choses sont censées fonctionner.

Reconcevoir votre approche pour saisir la nature asynchrone de ces événements. Si vous voulez par exemple une boîte de dialog qui demande à l’utilisateur une décision (comme accepter le ToS ou non) et faire des actions spéciales basées sur cette décision, créez une boîte de dialog comme celle-ci:

 AlertDialog dialog = new AlertDialog.Builder(context).setMessage(R.ssortingng.someText) .setPositiveButton(android.R.ssortingng.ok, new OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { dialog.dismiss(); // Do stuff if user accepts } }).setNegativeButton(android.R.ssortingng.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(); 

Ensuite, faites en sorte que deux méthodes gèrent la rétroaction positive ou négative (c.-à-d. En effectuant une opération ou en terminant l’activité ou tout ce qui a du sens).

Les développeurs d’Android et d’iOS ont décidé qu’ils étaient suffisamment puissants et intelligents pour rejeter la conception de Modal Dialog (qui était déjà sur le marché depuis de nombreuses années et qui ne dérangeait personne auparavant), malheureusement pour nous.

Voici ma solution, ça marche super bien:

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

Cela fonctionne pour moi: créez une activité comme dialog. Alors,

  1. Ajoutez ceci à votre manifeste pour l’activité:

    android: theme = “@ android: style / Theme.Dialog”

  2. Ajoutez ceci à onCréez votre activité

    setFinishOnTouchOutside (false);

  3. Remplacer onBackPressed dans votre activité:

    @Override public void onBackPressed () {// empêche “de revenir” en quittant cette activité}

Le premier donne à l’activité l’aspect du dialog. Les deux derniers le font se comporter comme un dialog modal.

Finalement, je me suis retrouvé avec une solution vraiment simple et directe.

Les personnes familiarisées avec la programmation Win32 savent peut-être comment implémenter une boîte de dialog modale. En général, il exécute une boucle de message nestede (par GetMessage / PostMessage) lorsqu’il existe une boîte de dialog modale. J’ai donc essayé d’implémenter mon propre dialog modal de manière traditionnelle.

Au début, Android n’a pas fourni d’interfaces pour l’injecter dans la boucle de messages de l’interface utilisateur, ou je n’en ai pas trouvé. Quand j’ai regardé la source, Looper.loop (), j’ai trouvé que c’était exactement ce que je voulais. Mais encore, MessageQueue / Message n’a pas fourni d’interfaces publiques. Heureusement, nous avons une reflection en Java. Fondamentalement, je viens de copier exactement ce que Looper.loop () a fait, cela bloquait le workflow et traitait toujours correctement les événements. Je n’ai pas testé le dialog modal nested, mais théoriquement cela fonctionnerait.

Voici mon code source,

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

J’espère que cela aiderait.

Ce n’est pas difficile.

Supposons que vous avez un indicateur sur votre activité propriétaire (nommée waiting_for_result ), chaque fois que votre activité est reprise:

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

Cela garantissait l’activité du propriétaire, à moins que la boîte de dialog modale ne soit ignorée, chaque fois que l’on essaie d’obtenir le focus, on passera à l’activité de dialog modal.

Une solution est:

  1. Mettez tout le code pour chaque bouton sélectionné dans l’auditeur de chaque bouton.
  2. alert.show(); doit être la dernière ligne de code dans la fonction appelant l’alerte. N’importe quel code après cette ligne n’attendra pas pour fermer l’alerte, mais s’exécutera immédiatement.

J’espère que ça aide!

Comme Hackbod et d’autres l’ont souligné, Android ne fournit délibérément pas de méthode pour effectuer des boucles d’événement nestedes. Je comprends les raisons de cela, mais il y a certaines situations qui les nécessitent. Dans notre cas, nous avons notre propre machine virtuelle sur différentes plates-formes et nous voulions la porter sur Android. En interne, il y a beaucoup d’endroits où une boucle d’événements nestede est nécessaire, et il n’est pas vraiment possible de réécrire le tout uniquement pour Android. Quoi qu’il en soit, voici une solution (essentiellement issue de Comment puis-je traiter des événements non bloquants sur Android?, Mais j’ai ajouté un délai d’expiration):

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

J’ai une solution similaire comme cinquième , mais c’est un peu plus simple et ne nécessite pas de reflection. Ma pensée était, pourquoi ne pas utiliser une exception pour quitter le looper. Donc, mon looper personnalisé se lit comme suit:

1) L’exception qui est lancée:

 final class KillException extends RuntimeException { } 

2) Le looper personnalisé:

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

L’utilisation du looper personnalisé est assez simple. Supposons que vous ayez un dialog foo, puis faites simplement ce qui suit lorsque vous voulez appeler le dialog foo modalement:

a) En appelant foo:

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

Dans la boîte de dialog foo, lorsque vous souhaitez quitter, vous appelez simplement la méthode quit du looper personnalisé. Cela ressemble à ceci:

b) En sortant de foo:

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

J’ai récemment rencontré des problèmes avec Android 5.1.1, n’appelez pas de boîte de dialog modale à partir du menu principal, mais affichez un événement qui appelle la boîte de dialog modale. Sans publication, le menu principal sera bloqué, et j’ai vu Looper :: pollInner () SIGSEGVs dans mon application.

Je ne suis pas sûr que ce soit 100% modal, car vous pouvez cliquer sur un autre composant pour fermer la boîte de dialog, mais j’ai été confondu avec les constructions de boucles et je propose cela comme une autre possibilité. Cela a bien fonctionné pour moi, alors j’aimerais partager l’idée. Vous pouvez créer et ouvrir la boîte de dialog dans une méthode, puis la fermer dans la méthode de rappel et le programme attendra la réponse à la boîte de dialog avant d’exécuter la méthode de rappel. Si vous exécutez ensuite le rest de la méthode de rappel dans un nouveau thread, la boîte de dialog se fermera également avant l’exécution du rest du code. La seule chose à faire est d’avoir une variable de boîte de dialog globale, de sorte que différentes méthodes puissent y accéder. Donc quelque chose comme ce qui suit peut fonctionner:

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

Utilisez un BroadcastReceiver qui appelle la méthode suivante requirejse dans l’activité.

End-end le code d’activité à dialogFragment.show (fragmentTransaction, TAG); et continuer dans onReceive () – je ne suis pas 100% positif mais je mettrais de l’argent que startActivityForResult (); est basé exactement sur ce concept.

Tant que cette méthode n’est pas appelée depuis le récepteur, le code attendra l’interaction de l’utilisateur sans ANR.

Méthode onCreateView de DialogFragment

 private static final Ssortingng ACTION_CONTINUE = "com.package.name.action_continue"; @Nullable @Override public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState) { View v = inflater.inflate(R.layout.fragment_dialog, container, false); Button ok_button = v.findViewById(R.id.dialog_ok_button); ok_button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Intent i = new Intent(); i.setAction(ACTION_CONTINUE); getActivity().getApplicationContext().sendBroadcast(i); dismiss(); } }); return v; } 

Cette méthode dépend de la génération d’une classe d’extension DialogFrament et de l’appel d’une instance de cette classe via l’activité.

Toutefois…

Simple, clair, facile et vraiment modal.