it-swarm.com.de

Android: Wie bekomme ich einen modalen Dialog oder ein ähnliches modales Verhalten?

In diesen Tagen arbeite ich daran, modale Dialoge in Android zu simulieren. Ich habe viel gegoogelt, es gibt viele Diskussionen, aber leider gibt es nicht viele Möglichkeiten, um modal zu werden. Hier ist ein Hintergrund,
Dialoge, Modale Dialoge und Blockin
Dialoge/AlertDialogs: So blockieren Sie die Ausführung, während der Dialog aktiv ist (.NET-Stil)

Es gibt keinen direkten Weg, um modales Verhalten zu erlangen. Dann habe ich drei mögliche Lösungen gefunden:
1. Verwenden Sie eine dialogbezogene Aktivität wie diese - Thread , aber ich kann die Hauptaktivität immer noch nicht wirklich auf die Rückkehr der Dialogaktivität warten lassen. Die Hauptaktivität wurde auf Stopp gesetzt und wurde dann neu gestartet.
2. Erstellen Sie einen Arbeitsthread und verwenden Sie die Thread-Synchronisierung. Es ist jedoch ein riesiger Refactoring-Job für meine App. Jetzt habe ich eine einzige Hauptaktivität und einen Dienst im Haupt-UI-Thread.
3. Übernehmen Sie die Ereignisbehandlung innerhalb einer Schleife, wenn ein modales Dialogfeld angezeigt wird, und beenden Sie die Schleife, wenn das Dialogfeld geschlossen wird. Tatsächlich ist es die Art und Weise, ein echtes modales Dialogfeld zu erstellen, wie es genau in Windows funktioniert. Ich habe immer noch keinen Prototyp auf diese Weise.

Ich würde es immer noch gerne mit einer dialogorientierten Aktivität simulieren,
1. Starten Sie die Dialogaktivität mit startActivityForResult ().
2. Ergebnis von onActivityResult () erhalten
Hier ist eine Quelle

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

Der Aufrufer von startAlertDialog blockiert die Ausführung und erwartet ein Ergebnis. StartAlertDialog kehrte natürlich sofort zurück und die Hauptaktivität ging in den STOP-Status, während DialogActivity aktiv war. 

Die Frage ist also, wie man die Hauptaktivität wirklich auf das Ergebnis warten lässt.
Vielen Dank.

52
fifth

Ich habe einen modalen Dialog während der Verwendung von:

setCancelable(false);

im DialogFragment (nicht im DialogBuilder).

65
meises

Es ist nicht so möglich, wie Sie es geplant haben. Erstens dürfen Sie den UI-Thread nicht blockieren. Ihre Bewerbung wird beendet. Zweitens müssen Sie die Lebenszyklusmethoden behandeln, die aufgerufen werden, wenn eine andere Aktivität mit startActivity gestartet wird (Ihre ursprüngliche Aktivität wird angehalten, während die andere Aktivität ausgeführt wird). Drittens könnten Sie es wahrscheinlich irgendwie hacken, indem Sie startAlertDialog() nicht aus dem UI-Thread mit Thread-Synchronisation (wie Object.wait()) und einigen AlertDialog verwenden. Ich empfehle Ihnen jedoch nachdrücklich , dies nicht zu tun. Es ist hässlich, wird sicher kaputt gehen und es ist einfach nicht die Art und Weise, wie die Dinge funktionieren sollen.

Überarbeiten Sie Ihren Ansatz, um die Asynchronität dieser Ereignisse zu erfassen. Wenn Sie zum Beispiel einen Dialog wünschen, der den Benutzer nach einer Entscheidung fragt (wie das Akzeptieren des ToS oder nicht) und spezielle Aktionen basierend auf dieser Entscheidung ausführt, erstellen Sie einen Dialog wie diesen:

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

Lassen Sie dann zwei Methoden mit positiver oder negativer Rückkopplung umgehen (d. H. Mit einer Operation fortfahren oder die Aktivität beenden oder was auch immer sinnvoll ist).

13
Stephan

Die Entwickler von Android und iOS haben entschieden, dass sie leistungsfähig und klug genug sind, um die Konzeption von Modal Dialog (die schon viele Jahre auf dem Markt war und bisher niemanden gestört hat) abzulehnen, leider für uns. 

Hier ist meine Lösung, es funktioniert super:

    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
art926

Dies funktioniert für mich: Erstellen Sie eine Aktivität als Dialog. Dann,

  1. Fügen Sie dies Ihrem Manifest für die Aktivität hinzu:

    Android: theme = "@ Android: style/Theme.Dialog"

  2. Fügen Sie dies zu onCreate Ihrer Aktivität hinzu

    setFinishOnTouchOutside (false); 

  3. Überschreiben Sie onBackPressed in Ihrer Aktivität:

    @Override Public void onBackPressed () { // verhindern, dass "zurück" diese Aktivität verlässt }

Die erste gibt der Aktivität den Dialog-Look. Die letzten beiden verhalten sich wie ein modaler Dialog.

5
Peri Hartman

Schließlich bekam ich eine wirklich einfache und einfache Lösung. 

Personen, die mit der Win32-Programmierung vertraut sind, wissen möglicherweise, wie sie einen modalen Dialog implementieren. Im Allgemeinen wird eine verschachtelte Nachrichtenschleife (von GetMessage/PostMessage) ausgeführt, wenn ein modales Dialogfeld angezeigt wird. Also habe ich versucht, meinen eigenen modalen Dialog auf traditionelle Weise umzusetzen. 

Zunächst bot Android keine Schnittstellen zum Einfügen in die Thread-Schleife der Benutzeroberfläche, oder ich fand keine. Als ich in Source, Looper.loop () nachgesehen habe, war es genau das, was ich wollte. Dennoch haben MessageQueue/Message keine öffentlichen Schnittstellen bereitgestellt. Zum Glück haben wir in Java nachgedacht ... Im Grunde habe ich genau das, was Looper.loop () getan hat, kopiert, den Workflow blockiert und Ereignisse trotzdem ordnungsgemäß verarbeitet. Ich habe keinen verschachtelten modalen Dialog getestet, aber theoretisch würde es funktionieren. 

Hier ist mein Quellcode,

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

Hoffentlich würde das helfen.

4
fifth

Eine Lösung ist:

  1. Geben Sie den gesamten Code für jede ausgewählte Schaltfläche in den Listener jeder Schaltfläche ein. 
  2. alert.show(); muss die letzte Codezeile in der Funktion sein, die den Alert aufruft. Jeder Code nach dieser Zeile wartet nicht, um die Warnung zu schließen, sondern wird sofort ausgeführt.

Hoffe hilfe!

1
Rambo VI

Ich bin nicht sicher, ob dies zu 100% modal ist, da Sie auf eine andere Komponente klicken können, um das Dialogfeld zu schließen. Ich wurde jedoch mit den Loops-Konstrukten verwirrt und biete dies als weitere Möglichkeit an. Es hat gut für mich funktioniert, also möchte ich die Idee teilen. Sie können das Dialogfeld in einer Methode erstellen und öffnen und dann in der Rückrufmethode schließen. Das Programm wartet auf die Antwort des Dialogfelds, bevor Sie die Rückrufmethode ausführen. Wenn Sie dann den Rest der Callback-Methode in einem neuen Thread ausführen, wird das Dialogfeld ebenfalls zuerst geschlossen, bevor der Rest des Codes ausgeführt wird. Das einzige, was Sie tun müssen, ist eine globale Dialogvariable, damit verschiedene Methoden darauf zugreifen können. So etwas wie das Folgende kann funktionieren:

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
DCS

Wie hackbod und andere darauf hingewiesen haben, bietet Android absichtlich keine Methode für das Verschachteln von Ereignisschleifen. Ich verstehe die Gründe dafür, aber es gibt bestimmte Situationen, die sie erfordern. In unserem Fall haben wir eine eigene virtuelle Maschine, die auf verschiedenen Plattformen ausgeführt wird, und wir wollten sie auf Android portieren. Intern gibt es viele Orte, an denen eine verschachtelte Ereignisschleife erforderlich ist, und es ist nicht wirklich möglich, das Ganze nur für Android umzuschreiben. Wie auch immer, hier ist eine Lösung (im Wesentlichen genommen aus Wie kann ich die Verarbeitung von nicht blockierenden Ereignissen auf Android durchführen , aber ich habe ein Timeout hinzugefügt):

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
CpnCrunch

Es ist nicht schwer. 

Angenommen, Sie haben ein Kennzeichen für Ihre Eigentümeraktivität (mit dem Namen waiting_for_result), wenn Ihre Aktivität wieder aufgenommen wird:

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

Dies garantiert die Eigentümeraktivität, sofern der modale Dialog nicht verworfen wird, wenn er versucht, den Fokus auf die modale Dialogaktivität zu übertragen. 

1
xandy

Ich habe eine ähnliche Lösung wie 5th , aber es ist ein bisschen Einfacher und braucht keine Reflektion. Ich dachte, warum nicht Eine Ausnahme verwenden, um den Looper zu beenden. Also mein Custom Looper Liest sich wie folgt:

1) Die Ausnahme, die ausgelöst wird:

final class KillException extends RuntimeException {
}

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

}

Die Verwendung des benutzerdefinierten Greifers ist ziemlich einfach. Nehmen wir an, Sie haben ein Dialogfenster, dann führen Sie einfach folgendes aus, wobei Sie das Dialogfenster modal aufrufen wollen:

a) Wenn Sie in Foo anrufen:

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

Wenn Sie den Dialog innerhalb des Dialogfensters verlassen möchten, rufen Sie einfach die Methode ___ des aufgerufenen Looper auf. Das sieht so aus:

b) Beim Beenden von Foo:

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

Ich habe in letzter Zeit einige Probleme mit 5.1.1 Android festgestellt....... Ein modales Dialogfeld nicht über das Hauptmenü aufrufen. Stattdessen ein Ereignis ausgeben, das das modale Dialogfeld aufruft. Ohne das Posting wird das Hauptmenü angehalten und ich habe Looper :: pollInner () SIGSEGVs in meiner App gesehen.

1
j4n bur53

Verwenden Sie einen BroadcastReceiver, der die nächste in der Aktivität erforderliche Methode aufruft. 

Der Aktivitätscode wird unter dialogFragment.show (fragmentTransaction, TAG) beendet. und setze es in onReceive () fort - ich bin nicht 100% positiv, aber ich würde Geld legen, das startActivityForResult () legt; basiert auf genau diesem Konzept. 

Bis diese Methode vom Empfänger aufgerufen wird, wartet der Code auf Benutzerinteraktion ohne ANR.

OnCreateView-Methode von DialogFragment

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

Diese Methode hängt davon ab, ob Sie eine DialogFrament-Erweiterungsklasse erstellen und über die Aktivität eine Instanz dieser Klasse aufrufen.

Jedoch...

Einfach, klar, einfach und wirklich modal.

0
me_