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

ces jours-ci, je travaille sur la simulation du Dialogue modal sur Android. J'ai beaucoup cherché sur Google, il y a beaucoup de discussions mais malheureusement il n'y a pas beaucoup d'options pour l'obtenir modale. Voici quelques informations,

Dialogues, Dialogues modaux et Blockin

Dialogs/ AlertDialogs: comment "bloquer l'exécution" pendant que le dialogue est en place (.net-style)

il n'y a pas de façon directe d'obtenir un comportement modal, alors je est venu avec 3 solutions possibles,

1. Utilisez une activité sur le thème de dialogue, comme ce fil dit, Mais je ne peux toujours pas faire activité principale vraiment attendre le retour de dialogue-activité. L'activité principale s'est arrêtée et a repris.

2. Construisez un thread d'ouvrier, et utilisez la synchronisation de thread. Cependant, c'est un énorme travail de remaniement pour mon application, maintenant j'ai une seule activité principale et un service à la fois dans le fil principal D'UI.

3. Reprendre la gestion des événements dans une boucle quand il y a un dialogue modal vers le haut, et quitter la boucle quand le dialogue est fermé. En fait, c'est la façon de construire un véritable dialogue modale ce qu'elle fait exactement dans Windows. Je n'ai toujours pas terminé le prototype de cette façon.

j'aimerais quand même le simuler avec une activité sur le thème du dialogue,

1. boîte de dialogue de démarrage de l'activité par startActivityForResult()

2. obtenir un 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 bloquera l'exécution et s'attend à un résultat retourné. Mais startAlertDialog est revenu immédiatement, bien sûr, et l'activité principale est entrée en état D'arrêt pendant que la Dialogactivité était en hausse.

donc la question Est, comment faire l'activité principale vraiment attendre le résultat?

Grâce.

50
demandé sur Community 2011-05-25 10:47:25

11 réponses

j'ai obtenu un Dialogue modal en utilisant:

setCancelable(false);

sur le DialogFragment (pas sur le DialogBuilder).

61
répondu meises 2013-02-25 07:29:18

ce n'est pas possible comme vous l'aviez prévu. Premièrement, vous n'êtes pas autorisé à bloquer le fil UI. Votre demande sera résilié. Deuxièmement, besoin de gérer les méthodes de cycle de vie qui sont appelées quand une autre activité est commencée avec startActivity (votre activité originale sera en pause pendant que l'autre activité est en cours d'exécution). Troisièmement, vous pourriez probablement d'une manière ou d'une autre le pirater en utilisant startAlertDialog() pas à partir du thread UI, avec la synchronisation du thread (comme Object.wait() ) et certains AlertDialog . Cependant, je fortement vous encourage à ne pas faire cela. Il est laid, va certainement casser et il est tout simplement pas la façon dont les choses sont prévues pour fonctionner.

modifiez votre approche pour saisir la nature asynchrone de ces événements. Si vous voulez par exemple un dialogue 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 un dialogue comme celui-ci:

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

alors ont deux méthodes pour traiter les commentaires positifs ou négatifs en conséquence (c.-à-d. procéder à une opération ou terminer l'activité ou ce qui a du sens).

13
répondu Stephan 2014-11-14 07:25:04

les développeurs D'Android et iOS ont décidé qu'ils sont assez puissants et intelligents pour rejeter la conception de dialogue Modal (qui était sur le marché depuis de nombreuses années déjà et ne dérangeait personne avant), malheureusement pour nous.

voici ma solution, ça marche 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;
    }
7
répondu art926 2012-07-14 01:45:46

cela fonctionne pour moi: créez une activité comme votre dialogue. Puis,

  1. ajoutez ceci à votre manifeste pour l'activité:

    android:thème="@android:style/Thème.Boîte de dialogue"

  2. ajoutez ceci à onCreate de votre activité

    setFinishOnTouchOutside (false);

  3. Remplacer onBackPressed dans votre activité:

    @Override public void onBackPressed() { // empêcher le "retour" de quitter cette activité }

le premier donne à l'activité le regard de dialogue. Les deux derniers le font se comporter comme un dialogue modal.

5
répondu Peri Hartman 2013-02-06 16:22:18

finalement j'ai fini avec une solution vraiment simple et directe.

les gens qui sont familiers avec la programmation Win32 savent peut-être comment implémenter un dialogue modal. Généralement il exécute une boucle de message imbriquée (par GetMessage/PostMessage) quand il y a un dialogue modal up. Donc, j'ai essayé d'implémenter mon propre Dialogue modal de cette manière traditionnelle.

à la première, android n'a pas fourni d'interfaces pour injecter dans la boucle de message d'ui thread, ou je n'ai pas en trouver un. Quand J'ai cherché la source, Looper.loop(), j'ai trouvé, c'est exactement ce que je voulais. Mais malgré tout, MessageQueue/Message n'a pas fourni d'interfaces publiques. Heureusement, nous avons une réflexion à java. En gros, je viens de copier exactement ce que Looper.loop () l'a fait, il a bloqué le flux de travail et a toujours bien géré les événements. Je n'ai pas testé le Dialogue modal imbriqué, mais théoriquement ça marcherait.

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

espérons que cela aider.

4
répondu fifth 2011-06-01 08:19:01

ce n'est pas difficile.

supposons que vous ayez un drapeau sur votre activité de propriétaire (nommé 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, sauf si le Dialogue modal est rejeté, chaque fois qu'il essaie d'obtenir la mise au point passera à l'activité de dialogue modal.

1
répondu xandy 2011-05-25 06:55:12

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 d'appel de l'Alerte. Tout code postérieur à cette ligne n'attendra pas pour fermer l'alerte, mais s'exécutera immédiatement.

L'Espoir De L'Aide!

1
répondu Rambo VI 2012-10-02 08:04:05

comme hackbod et d'autres l'ont souligné, Android délibérément ne fournit pas une méthode pour faire des boucles d'événements imbriquées. Je comprends les raisons de cette situation, mais certaines situations l'exigent. Dans notre cas, nous avons notre propre machine virtuelle fonctionnant sur diverses plates-formes et nous voulions le porter à Android. En interne, Il ya beaucoup d'endroits où il nécessite une boucle d'événements imbriqués, et il n'est pas vraiment possible de réécrire l'ensemble de la chose juste pour Android. De toute façon, ici, c'est un solution (essentiellement tiré de Comment puis-je faire le traitement des événements non-bloquants sur Android? , mais j'ai ajouté un timeout):

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
répondu CpnCrunch 2017-05-23 12:26:15

j'ai une solution similaire comme cinquième , mais c'est un peu plus simple et n'a pas besoin de réflexion. Ma pensée était, pourquoi pas utilisez une exception pour sortir du looper. Alors mon looper personnalisé se lit comme suit:

1) l'exception qui est jetée:

final class KillException extends RuntimeException {
}

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

}

l'utilisation du looper personnalisé est très simple. Supposer vous avez un dialogue foo, puis tout simplement faire le suivant où vous voulez appeler le dialogue foo modal:

A) lors d'un appel à foo:

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

dans le dialogue foo, quand vous voulez sortir, vous simplement appelez la méthode quit du Looper personnalisé. Cela ressemble comme suit:

b) à la sortie de foo:

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

j'ai vu récemment quelques problèmes avec 5.1.1 Android, ne pas appeler un dialogue modal à partir du menu principal, au lieu de poster un événement qui appelle la boîte de dialogue modale. Sans affichage de la menu principal va décrocher, et j'ai vu Looper:: pollInner() SIGSEGVs dans mon application.

1
répondu j4n bur53 2017-05-23 12:26:15

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 dialogue, mais j'ai été confondu avec les constructions de boucles et donc j'offre ceci comme une autre possibilité. Cela a bien fonctionné pour moi, donc je voudrais partager l'idée. Vous pouvez créer et ouvrir la boîte de dialogue dans une méthode, puis la fermer dans la méthode de rappel et le programme attendra la réponse de dialogue avant d'exécuter la méthode de rappel. Si vous exécutez ensuite le reste de la méthode de rappel dans un nouveau thread, la boîte de dialogue se fermera aussi en premier, avant que le reste du code ne soit exécuté. La seule chose que vous devez faire est d'avoir une variable de boîte de dialogue globale, de sorte que différentes méthodes peuvent y accéder. Ainsi, 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();
    }
}
1
répondu DCS 2016-01-27 12:32:22

utilisez un émetteur qui appelle la méthode suivante requise dans l'activité.

Sans objet le code d'activité au dialogFragment.show (fragmentTransaction, TAG); and continue it in onReceive ()--Je ne suis pas 100% positif mais je mettrais de l'argent que startActivityForResult (); est basé exactement sur ce concept.

Jusqu'à ce que cette méthode soit invoquée par le récepteur, le code attendra l'interaction de l'utilisateur sans ANR.

Dialogfragment's onreateview Method

private static final String 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 construction d'une classe D'extension de dialogue et d'appeler une instance de cette classe par l'activité.

cependant...

Simple, clair, facile et vraiment modal.

0
répondu me_ 2018-07-27 13:04:50