Verhindern Sie das Schließen von Dialogen bei der Bildschirmrotation in Android

94

Ich versuche zu verhindern, dass mit dem Alert Builder erstellte Dialoge beim Neustart der Aktivität geschlossen werden.

Wenn ich die onConfigurationChanged-Methode überlade, kann ich dies erfolgreich tun und das Layout auf die richtige Ausrichtung zurücksetzen, aber ich verliere die Sticky-Text-Funktion von edittext. Bei der Lösung des Dialogproblems habe ich dieses Edittext-Problem erstellt.

Wenn ich die Zeichenfolgen aus dem Edittext speichere und sie in der onCofiguration-Änderung neu zuordne, scheinen sie immer noch standardmäßig den Anfangswert zu haben, nicht den, der vor der Drehung eingegeben wurde. Selbst wenn ich eine Ungültigmachung erzwinge, scheinen sie zu aktualisieren.

Ich muss wirklich entweder das Dialogproblem oder das Edittext-Problem lösen.

Danke für die Hilfe.

Draksie
quelle
1
Wie speichere / stelle ich den Inhalt des bearbeiteten EditText wieder her? Können Sie einen Code zeigen?
Peter Knego
Ich habe das Problem damit herausgefunden und vergessen, die Ansicht nach dem Zurücksetzen des Layouts erneut per ID abzurufen.
Draksia

Antworten:

134

Der beste Weg, um dieses Problem heutzutage zu vermeiden, ist die Verwendung von a DialogFragment.

Erstellen Sie eine neue Klasse, die erweitert wird DialogFragment. Überschreiben Sie onCreateDialogund geben Sie Ihr altes Dialogoder ein AlertDialog.

Dann können Sie es mit zeigen DialogFragment.show(fragmentManager, tag).

Hier ist ein Beispiel mit einem Listener:

public class MyDialogFragment extends DialogFragment {

    public interface YesNoListener {
        void onYes();

        void onNo();
    }

    @Override
    public void onAttach(Activity activity) {
        super.onAttach(activity);
        if (!(activity instanceof YesNoListener)) {
            throw new ClassCastException(activity.toString() + " must implement YesNoListener");
        }
    }

    @Override
    public Dialog onCreateDialog(Bundle savedInstanceState) {
        return new AlertDialog.Builder(getActivity())
                .setTitle(R.string.dialog_my_title)
                .setMessage(R.string.dialog_my_message)
                .setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() {

                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        ((YesNoListener) getActivity()).onYes();
                    }
                })
                .setNegativeButton(android.R.string.no, new DialogInterface.OnClickListener() {

                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        ((YesNoListener) getActivity()).onNo();
                    }
                })
                .create();
    }
}

Und in der Aktivität, die Sie anrufen:

new MyDialogFragment().show(getSupportFragmentManager(), "tag"); // or getFragmentManager() in API 11+

Diese Antwort hilft bei der Erklärung dieser anderen drei Fragen (und ihrer Antworten):

Brais Gabin
quelle
In meiner App gibt es eine Schaltfläche zum Aufrufen von .show (). Ich muss mich an den Status des Warndialogs erinnern, der angezeigt / geschlossen wird. Gibt es eine Möglichkeit, den Dialog beizubehalten, ohne .show () aufzurufen?
Alpha Huang
1
Es heißt, dass dies onAttachjetzt veraltet ist. Was ist stattdessen zu tun?
Farahm
3
@faraz_ahmed_kamran, sollten Sie onAttach(Context context)und verwenden android.support.v4.app.DialogFragment. Die onAttachMethode nimmt jetzt contextstatt activityals Parameter.
Stan Mots
Es besteht jedoch wahrscheinlich keine Notwendigkeit dafür YesNoListener. Siehe diese Antwort .
Mygod
46
// Prevent dialog dismiss when orientation changes
private static void doKeepDialog(Dialog dialog){
    WindowManager.LayoutParams lp = new WindowManager.LayoutParams();
    lp.copyFrom(dialog.getWindow().getAttributes());
    lp.width = WindowManager.LayoutParams.WRAP_CONTENT;
    lp.height = WindowManager.LayoutParams.WRAP_CONTENT;
    dialog.getWindow().setAttributes(lp);
}
public static void doLogout(final Context context){     
        final AlertDialog dialog = new AlertDialog.Builder(context)
        .setIcon(android.R.drawable.ic_dialog_alert)
        .setTitle(R.string.titlelogout)
        .setMessage(R.string.logoutconfirm)
        .setPositiveButton("Yes", new DialogInterface.OnClickListener()
        {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                ...   
            }

        })
        .setNegativeButton("No", null)      
        .show();    

        doKeepDialog(dialog);
    }
Chung IW
quelle
1
Für wen mein Code nicht nützlich ist, versuchen Sie es, bevor Sie klicken :-)
Chung IW
6
Funktioniert, weiß nicht wie, aber es funktioniert! Saubere, einfache und abstrakte Lösung, danke.
nmvictor
3
Ich denke, dieser Code ist schlecht. doLogout () verweist auf den Kontext, der die Aktivität enthält. Die Aktivität kann nicht zerstört werden, was zu einem Speicherverlust führen kann. Ich habe nach einer Möglichkeit gesucht, AlertDialog aus dem statischen Kontext zu verwenden, aber jetzt bin ich sicher, dass dies unmöglich ist. Das Ergebnis kann nur Müll sein, denke ich.
Der unglaubliche
2
Es sieht einfach so aus, als würde es funktionieren. Der Dialog bleibt geöffnet, hat jedoch keine Verbindung zu der neu erstellten Aktivität oder dem neu erstellten Fragment (er wird bei jeder Orientierungsänderung neu erstellt). Sie können also nichts tun, was ein ContextEinfügen in die Dialogschaltflächen erfordert OnClickListener.
Udo Klimaschewski
2
Dieser Code funktioniert, wird aber überhaupt nicht empfohlen. Es wird eine Aktivitätsreferenz verloren, weshalb der Dialog dauerhaft bleiben kann. Dies ist eine sehr schlechte Vorgehensweise, die zu einem Speicherverlust führt.
Hexise
4

Wenn Sie das Layout bei einer Orientierungsänderung ändern, würde ich es nicht sagen android:configChanges="orientation" in Ihr Manifest aufnehmen, da Sie die Ansichten sowieso neu erstellen.

Speichern Sie den aktuellen Status Ihrer Aktivität (wie eingegebener Text, angezeigter Dialog, angezeigte Daten usw.) mit den folgenden Methoden:

@Override
protected void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);
}

@Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
    super.onRestoreInstanceState(savedInstanceState);
}

Auf diese Weise durchläuft die Aktivität erneut onCreate und ruft anschließend die onRestoreInstanceState-Methode auf, mit der Sie Ihren EditText-Wert erneut festlegen können.

Wenn Sie komplexere Objekte speichern möchten, können Sie diese verwenden

@Override
public Object onRetainNonConfigurationInstance() {
}

Hier können Sie jedes Objekt speichern und in onCreate müssen Sie nur aufrufen getLastNonConfigurationInstance();, um das Objekt zu erhalten.

Maria Neumayer
quelle
4
OnRetainNonConfigurationInstance()ist jetzt veraltet, wie der Doc sagt: developer.android.com/reference/android/app/… setRetainInstance(boolean retain) sollte stattdessen verwendet werden: developer.android.com/reference/android/app/…
ForceMagic
@ForceMagic setRetainInstanceist völlig anders: Es ist für Fragmente und garantiert nicht, dass die Instanz beibehalten wird.
Miha_x64
3

Fügen Sie einfach android: configChanges = "Orientierung" mit Ihrem Aktivitätselement in AndroidManifest.xml hinzu

Beispiel:

<activity
            android:name=".YourActivity"
            android:configChanges="orientation"
            android:label="@string/app_name"></activity>
SAAM
quelle
Dies kann unter bestimmten Umständen dazu führen, dass der Dialog falsch angezeigt wird.
Sam
1
android: configChanges = "Ausrichtung | Bildschirmgröße" Hinweis: Wenn Ihre Anwendung auf Android 3.2 (API-Stufe 13) oder höher abzielt, sollten Sie auch die Konfiguration "screenSize" deklarieren, da sie sich auch ändert, wenn ein Gerät zwischen Hoch- und Querformat wechselt.
Tsig
1

Ein sehr einfacher Ansatz besteht darin, die Dialoge aus der Methode zu erstellen onCreateDialog()(siehe Hinweis unten). Sie zeigen sie durch showDialog(). Auf diese Weise, Android übernimmt die Rotation für Sie und Sie müssen nicht nennen dismiss()in onPause()einer WindowLeak zu vermeiden und dann weder Sie müssen den Dialog wiederherzustellen. Aus den Dokumenten:

Zeigen Sie einen Dialog an, der von dieser Aktivität verwaltet wird. Ein Aufruf von onCreateDialog (int, Bundle) erfolgt mit derselben ID, wenn dies zum ersten Mal für eine bestimmte ID aufgerufen wird. Danach wird der Dialog automatisch gespeichert und wiederhergestellt.

Weitere Informationen finden Sie in den Android-Dokumenten showDialog () . Hoffe es hilft jemandem!

Hinweis: Wenn Sie AlertDialog.Builder verwenden, rufen Sie nicht show()von an onCreateDialog(), create()sondern rufen Sie stattdessen an. Wenn Sie ProgressDialog verwenden, erstellen Sie einfach das Objekt, legen Sie die gewünschten Parameter fest und geben Sie es zurück. Zusammenfassend lässt sich sagen, dass show()Inside onCreateDialog()Probleme verursacht. Erstellen Sie einfach die de Dialog-Instanz und geben Sie sie zurück. Das sollte funktionieren! (Ich habe Probleme bei der Verwendung von showDialog () von onCreate () festgestellt - der Dialog wird tatsächlich nicht angezeigt -, aber wenn Sie ihn in onResume () oder in einem Listener-Rückruf verwenden, funktioniert er gut).

Caumons
quelle
Für welchen Fall benötigen Sie Code? Der onCreateDialog () oder das Anzeigen mit dem Builder und das Aufrufen von show ()?
Caumons
Ich habe es geschafft .. aber die Sache ist, onCreateDialog () ist jetzt veraltet: - \
neteinstein
OK! Denken Sie daran, dass die meisten Android-Geräte immer noch mit 2.X-Versionen funktionieren, sodass Sie sie trotzdem verwenden können!
Werfen
Was ist die andere Option, wenn nicht onCreateDialog?
Neteinstein
1
Sie können die Builder-Klassen verwenden, z AlertDialog.Builder. Wenn Sie es im Inneren verwenden onCreateDialog(), geben Sie anstelle von show()return das Ergebnis von zurück create(). show()Andernfalls rufen Sie den zurückgegebenen AlertDialog auf und speichern ihn in einem Attribut der Aktivität und onPause() dismiss()darin, wenn er angezeigt wird, um ein WindowLeak zu vermeiden. Ich hoffe es hilft!
Caumons
1

Diese Frage wurde vor langer Zeit beantwortet.

Dies ist jedoch nicht hacky und einfach Lösung, die ich für mich selbst verwende.

Ich habe diese Helferklasse gemacht für mich selbst gemacht, damit Sie sie auch in Ihrer Anwendung verwenden können.

Verwendung ist:

PersistentDialogFragment.newInstance(
        getBaseContext(),
        RC_REQUEST_CODE,
        R.string.message_text,
        R.string.positive_btn_text,
        R.string.negative_btn_text)
        .show(getSupportFragmentManager(), PersistentDialogFragment.TAG);

Oder

 PersistentDialogFragment.newInstance(
        getBaseContext(),
        RC_EXPLAIN_LOCATION,
        "Dialog title", 
        "Dialog Message", 
        "Positive Button", 
        "Negative Button", 
        false)
    .show(getSupportFragmentManager(), PersistentDialogFragment.TAG);





public class ExampleActivity extends Activity implements PersistentDialogListener{

        @Override
        void onDialogPositiveClicked(int requestCode) {
                switch(requestCode) {
                  case RC_REQUEST_CODE:
                  break;
                }
        }

        @Override
        void onDialogNegativeClicked(int requestCode) {
                switch(requestCode) {
                  case RC_REQUEST_CODE:
                  break;
                }          
        }
}
Ioane Sharvadze
quelle
1

Sie können die onSave / onRestore- Methoden des Dialogfelds mit den onSave / onRestore- Methoden der Aktivität kombinieren , um den Status des Dialogfelds beizubehalten .

Hinweis: Diese Methode eignet sich für "einfache" Dialoge, z. B. zum Anzeigen einer Warnmeldung. Der Inhalt einer in einen Dialog eingebetteten WebView wird nicht reproduziert. Wenn Sie wirklich verhindern möchten, dass ein komplexer Dialog während der Rotation geschlossen wird, versuchen Sie die Methode von Chung IW.

@Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
     super.onRestoreInstanceState(savedInstanceState);
     myDialog.onRestoreInstanceState(savedInstanceState.getBundle("DIALOG"));
     // Put your codes to retrieve the EditText contents and 
     // assign them to the EditText here.
}

@Override
protected void onSaveInstanceState(Bundle outState) {
     super.onSaveInstanceState(outState);
     // Put your codes to save the EditText contents and put them 
     // to the outState Bundle here.
     outState.putBundle("DIALOG", myDialog.onSaveInstanceState());
}
Hackjutsu
quelle
1

Der beste Ansatz ist auf jeden Fall die Verwendung von DialogFragment.

Hier ist meine Lösung der Wrapper-Klasse, die verhindert, dass verschiedene Dialoge innerhalb eines Fragments (oder einer Aktivität mit kleinem Refactoring) geschlossen werden. Es hilft auch, massives Code-Refactoring zu vermeiden, wenn aus bestimmten Gründen viele AlertDialogsim Code verstreut sind und sich geringfügig in Bezug auf Aktionen, Erscheinungsbild oder etwas anderes unterscheiden.

public class DialogWrapper extends DialogFragment {
    private static final String ARG_DIALOG_ID = "ARG_DIALOG_ID";

    private int mDialogId;

    /**
     * Display dialog fragment.
     * @param invoker  The fragment which will serve as {@link AlertDialog} alert dialog provider
     * @param dialogId The ID of dialog that should be shown
     */
    public static <T extends Fragment & DialogProvider> void show(T invoker, int dialogId) {
        Bundle args = new Bundle();
        args.putInt(ARG_DIALOG_ID, dialogId);
        DialogWrapper dialogWrapper = new DialogWrapper();
        dialogWrapper.setArguments(args);
        dialogWrapper.setTargetFragment(invoker, 0);
        dialogWrapper.show(invoker.getActivity().getSupportFragmentManager(), null);
    }

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mDialogId = getArguments().getInt(ARG_DIALOG_ID);
    }

    @NonNull
    @Override
    public Dialog onCreateDialog(Bundle savedInstanceState) {
        return getDialogProvider().getDialog(mDialogId);
    }

    private DialogProvider getDialogProvider() {
        return (DialogProvider) getTargetFragment();
    }

    public interface DialogProvider {
        Dialog getDialog(int dialogId);
    }
}

Wenn es um die Aktivität kommt , kann man aufrufen getContext()innen onCreateDialog(), werfen es in die DialogProviderSchnittstelle und anfordern , indem Sie einen bestimmten DialogmDialogId . Alle Logik zum Umgang mit einem Zielfragment sollte gelöscht werden.

Verwendung aus Fragment:

public class MainFragment extends Fragment implements DialogWrapper.DialogProvider {
    private static final int ID_CONFIRMATION_DIALOG = 0;

    @Override
    public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
        Button btnHello = (Button) view.findViewById(R.id.btnConfirm);
        btnHello.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                DialogWrapper.show(MainFragment.this, ID_CONFIRMATION_DIALOG);
            }
        });
    }

    @Override
    public Dialog getDialog(int dialogId) {
        switch (dialogId) {
            case ID_CONFIRMATION_DIALOG:
                return createConfirmationDialog(); //Your AlertDialog
            default:
                throw new IllegalArgumentException("Unknown dialog id: " + dialogId);
        }
    }
}

Sie können den vollständigen Artikel in meinem Blog lesen. Wie kann verhindert werden, dass Dialog entlassen wird? und mit dem Quellcode spielen .

Dmitry Korobeinikov
quelle
1

Es scheint, dass dies immer noch ein Problem ist, selbst wenn "alles richtig gemacht" und verwendet wird DialogFragment usw. verwendet wird.

In Google Issue Tracker gibt es einen Thread, der behauptet, dass eine alte Entlassungsnachricht in der Nachrichtenwarteschlange verbleibt. Die bereitgestellte Problemumgehung ist recht einfach:

    @Override
    public void onDestroyView() {
        /* Bugfix: https://issuetracker.google.com/issues/36929400 */
        if (getDialog() != null && getRetainInstance())
            getDialog().setDismissMessage(null);

        super.onDestroyView();
    }

Unglaublich, dass dies noch 7 Jahre nach der ersten Meldung dieses Problems erforderlich ist.

Magnus W.
quelle
0

Ich hatte ein ähnliches Problem: Als sich die Bildschirmausrichtung änderte, wurde der onDismissListener des Dialogfelds aufgerufen, obwohl der Benutzer das Dialogfeld nicht geschlossen hatte. Ich konnte dies umgehen, indem ich stattdessen den onCancelListener verwendete, der sowohl beim Drücken der Zurück-Taste als auch beim Berühren des Benutzers außerhalb des Dialogfelds ausgelöst wurde.

Sam
quelle
-3

Benutz einfach

ConfigurationChanges = Android.Content.PM.ConfigChanges.Orientation | Android.Content.PM.ConfigChanges.ScreenSize

und die App weiß, wie man mit Rotation und Bildschirmgröße umgeht.

user3384694
quelle