Impedisci la chiusura delle finestre di dialogo durante la rotazione dello schermo in Android


94

Sto cercando di impedire che le finestre di dialogo create con Alert Builder vengano chiuse quando l'attività viene riavviata.

Se sovraccarico il metodo onConfigurationChanged, posso farlo con successo e ripristinare il layout per correggere l'orientamento ma perdo la funzione di testo appiccicoso di edittext. Quindi, risolvendo il problema del dialogo, ho creato questo problema di testo.

Se salvo le stringhe da edittext e le riassegno nella modifica onCofiguration, sembrano ancora essere predefinite al valore iniziale, non a quello inserito prima della rotazione. Anche se forzo un annullamento, sembra che li aggiorni.

Ho davvero bisogno di risolvere il problema della finestra di dialogo o il problema dell'edittext.

Grazie per l'aiuto.


1
Come si salva / ripristina il contenuto dell'EditText modificato? Puoi mostrare del codice?
Peter Knego

Ho capito il problema, mi stavo dimenticando di ottenere di nuovo la vista da Id dopo aver ripristinato il layout.
draksia

Risposte:


134

Il modo migliore per evitare questo problema oggigiorno è utilizzare un file DialogFragment.

Crea una nuova classe che si estende DialogFragment. Ignora onCreateDialoge restituisci il tuo vecchio Dialogo un file AlertDialog.

Quindi puoi mostrarlo con DialogFragment.show(fragmentManager, tag).

Ecco un esempio con 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();
    }
}

E nell'attività che chiami:

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

Questa risposta aiuta a spiegare queste altre tre domande (e le loro risposte):


C'è un pulsante nella mia app per chiamare .show (), devo ricordare lo stato della finestra di dialogo di avviso, mostrato / ignorato. C'è un modo per mantenere la finestra di dialogo senza chiamare .show ()?
Alpha Huang

1
Dice che onAttachora è deprecato. Cosa si dovrebbe fare invece?
farahm

3
@faraz_ahmed_kamran, dovresti usare onAttach(Context context)e android.support.v4.app.DialogFragment. Il onAttachmetodo ora prende contextinvece che activitycome parametro.
Stan Mots,

Probabilmente non ce n'è bisogno YesNoListener. Vedi questa risposta .
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);
    }

1
Per chi trova il mio codice non utile, provalo prima di cliccare :-)
Chung IW

6
Funziona, non so come ma funziona! Soluzione pulita semplice e astratta, grazie.
nmvictor

3
Penso che questo codice sia sbagliato. doLogout () ha un riferimento al contesto che è / contiene l'attività. Impossibile eliminare l'attività che può causare una perdita di memoria. Stavo cercando la possibilità di utilizzare AlertDialog dal contesto statico, ma ora sono sicuro che sia impossibile. Il risultato può essere solo spazzatura, credo.
L'incredibile

2
Sembra che funzioni. La finestra di dialogo rimane aperta, ma non ha alcuna connessione con l'attività o il frammento appena creato (viene creato di recente ad ogni cambio di orientamento). Quindi non puoi fare nulla che richieda un Contextall'interno dei pulsanti di dialogo OnClickListener.
Udo Klimaschewski

2
Questo codice funziona ma non è affatto consigliato. Perde il riferimento all'attività, motivo per cui il dialogo può persistere. Questa è una pessima pratica che porterà alla perdita di memoria.
Hexise

4

Se stai cambiando il layout al cambio di orientamento, non metterei il android:configChanges="orientation"tuo manifest perché stai comunque ricreando le viste.

Salva lo stato corrente della tua attività (come testo inserito, finestra di dialogo mostrata, dati visualizzati ecc.) Utilizzando questi metodi:

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

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

In questo modo l'attività passa di nuovo attraverso onCreate e successivamente chiama il metodo onRestoreInstanceState in cui è possibile impostare nuovamente il valore EditText.

Se vuoi memorizzare oggetti più complessi puoi usare

@Override
public Object onRetainNonConfigurationInstance() {
}

Qui puoi memorizzare qualsiasi oggetto e in onCreate devi solo chiamare getLastNonConfigurationInstance();per ottenere l'oggetto.


4
OnRetainNonConfigurationInstance()è ora deprecato come dice il documento: developer.android.com/reference/android/app/… setRetainInstance(boolean retain) dovrebbe essere usato invece: developer.android.com/reference/android/app/…
ForceMagic

@ForceMagic setRetainInstanceè completamente diverso: è per Fragments e non ti garantisce che l'istanza verrà conservata.
Miha_x64

3

Basta aggiungere android: configChanges = "orientamento" con l'elemento attività in AndroidManifest.xml

Esempio:

<activity
            android:name=".YourActivity"
            android:configChanges="orientation"
            android:label="@string/app_name"></activity>

Ciò può causare la visualizzazione non corretta della finestra di dialogo in alcune circostanze.
Sam

1
android: configChanges = "orientamento | screenSize" Nota: se la tua applicazione è destinata ad Android 3.2 (livello API 13) o superiore, dovresti anche dichiarare la configurazione "screenSize", perché cambia anche quando un dispositivo passa dall'orientamento verticale a quello orizzontale.
tsig

1

Un approccio molto semplice consiste nel creare le finestre di dialogo dal metodo onCreateDialog()(vedere la nota sotto). Li mostri attraverso showDialog(). In questo modo, Android gestisce la rotazione per voi e non c'è bisogno di chiamare dismiss()in onPause()per evitare una WindowLeak e poi non essere necessario ripristinare la finestra di dialogo. Dai documenti:

Mostra una finestra di dialogo gestita da questa attività. Una chiamata a onCreateDialog (int, Bundle) verrà effettuata con lo stesso id la prima volta che viene chiamato per un dato id. Da quel momento in poi, la finestra di dialogo verrà automaticamente salvata e ripristinata.

Per ulteriori informazioni, vedere la documentazione di Android showDialog () . Spero che aiuti qualcuno!

Nota: se si utilizza AlertDialog.Builder, non chiamare show()da onCreateDialog(), chiama create()invece. Se si utilizza ProgressDialog, è sufficiente creare l'oggetto, impostare i parametri necessari e restituirlo. In conclusione, show()all'interno onCreateDialog()provoca problemi, basta creare l'istanza di de Dialog e restituirla. Questo dovrebbe funzionare! (Ho riscontrato problemi usando showDialog () da onCreate () -attualmente non mostra la finestra di dialogo-, ma se lo usi in onResume () o in un callback listener funziona bene).


Per quale caso avresti bisogno del codice? Il onCreateDialog () o mostrandolo con il builder e chiamando show () ad esso?
Caumons

Sono riuscito a farlo .. ma il fatto è che onCreateDialog () è ora deprecato: - \
neteinstein

OK! Tieni presente che la maggior parte dei dispositivi Android funziona ancora con le versioni 2.X, quindi puoi usarla comunque!
Dai

Tuttavia, qual è l'altra opzione se non suCreateDialog?
neteinstein

1
Puoi usare le classi builder es AlertDialog.Builder. Se lo usi all'interno onCreateDialog(), invece di usare show()restituisci il risultato di create(). Altrimenti, chiama show()e archivia il AlertDialog restituito in un attributo dell'attività e in onPause() dismiss()esso se viene visualizzato per evitare un WindowLeak. Spero che sia d'aiuto!
Caumons

1

Questa domanda ha avuto risposta molto tempo fa.

Eppure questa è una soluzione semplice e non hacker che uso per me stesso.

Ho fatto questa classe di supporto per me stesso, quindi puoi usarla anche nella tua applicazione.

L'utilizzo è:

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

O

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

1

È possibile combinare i metodi onSave / onRestore della finestra di dialogo con i metodi onSave / onRestore dell'attività per mantenere lo stato della finestra di dialogo.

Nota: questo metodo funziona per quelle finestre di dialogo "semplici", come la visualizzazione di un messaggio di avviso. Non riprodurrà il contenuto di una WebView incorporata in una finestra di dialogo. Se vuoi davvero impedire che una finestra di dialogo complessa venga chiusa durante la rotazione, prova il metodo di 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());
}

1

Sicuramente, l'approccio migliore è usare DialogFragment.

Ecco la mia soluzione della classe wrapper che aiuta a impedire che diversi dialoghi vengano ignorati all'interno di un frammento (o attività con piccolo refactoring). Inoltre, aiuta a evitare un massiccio refactoring del codice se per qualche motivo ce ne sono molti AlertDialogssparsi nel codice con lievi differenze tra loro in termini di azioni, aspetto o qualcos'altro.

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

Quando si tratta di attività puoi invocare getContext()all'interno onCreateDialog(), trasmetterlo all'interfaccia DialogProvidere richiedere una finestra di dialogo specifica da mDialogId. Tutta la logica per gestire un frammento di destinazione dovrebbe essere eliminata.

Utilizzo dal frammento:

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

Puoi leggere l'articolo completo sul mio blog Come evitare che il dialogo venga chiuso? e gioca con il codice sorgente .


1

Sembra che questo sia ancora un problema, anche quando "fai tutto bene" e usi DialogFragmentecc.

C'è un thread su Google Issue Tracker che afferma che è dovuto a un vecchio messaggio di eliminazione lasciato nella coda dei messaggi. La soluzione alternativa fornita è abbastanza semplice:

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

        super.onDestroyView();
    }

È incredibile che questo sia ancora necessario 7 anni dopo che il problema è stato segnalato per la prima volta.



0

Ho avuto un problema simile: quando l'orientamento dello schermo cambiava, il onDismisslistener della finestra di dialogo veniva chiamato anche se l'utente non chiudeva la finestra di dialogo. Sono stato in grado di aggirare questo problema utilizzando invece l' onCancelascoltatore, che si attivava sia quando l'utente premeva il pulsante Indietro sia quando l'utente toccava al di fuori della finestra di dialogo.


-3

Basta usare

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

e l'app saprà come gestire la rotazione e le dimensioni dello schermo.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.