Zapobiegaj zamykaniu okien dialogowych przy obracaniu ekranu w systemie Android


94

Próbuję zapobiec zamykaniu okien dialogowych utworzonych za pomocą narzędzia Alert Builder po ponownym uruchomieniu działania.

Jeśli przeciążę metodę onConfigurationChanged, mogę z powodzeniem to zrobić i zresetować układ do prawidłowej orientacji, ale stracę funkcję lepkiego tekstu w edytorze. Więc rozwiązując problem z dialogiem stworzyłem ten problem z edittextem.

Jeśli zapiszę ciągi z edycji edittext i przypiszę je ponownie w zmianie onCofiguration, nadal wydaje się, że domyślnie mają one wartość początkową, a nie to, co zostało wprowadzone przed rotacją. Nawet jeśli wymuszę, unieważniony wydaje się je aktualizować.

Naprawdę muszę rozwiązać problem z dialogiem lub z edittextem.

Dzięki za pomoc.


1
Jak zapisać / przywrócić zawartość edytowanego EditText? Czy możesz pokazać kod?
Peter Knego

Zrozumiałem problem z tym, zapomniałem uzyskać widok ponownie przez Id po zresetowaniu układu.
draksia

Odpowiedzi:


134

Obecnie najlepszym sposobem uniknięcia tego problemu jest użycie pliku DialogFragment.

Utwórz nową klasę, która rozszerza DialogFragment. Zastąp onCreateDialogi zwróć stary Dialoglub plik AlertDialog.

Następnie możesz to pokazać za pomocą DialogFragment.show(fragmentManager, tag).

Oto przykład z 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();
    }
}

A w wywołanym działaniu:

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

Ta odpowiedź pomaga wyjaśnić te pozostałe trzy pytania (i ich odpowiedzi):


W mojej aplikacji jest przycisk do wywołania .show (), muszę zapamiętać stan okna dialogowego z ostrzeżeniem, pokazującego / odrzuconego. Czy istnieje sposób na zachowanie okna dialogowego bez wywoływania .show ()?
Alpha Huang

1
Mówi, że onAttachjest teraz przestarzałe. Co należy zrobić zamiast tego?
farahm

3
@faraz_ahmed_kamran, powinieneś użyć onAttach(Context context)i android.support.v4.app.DialogFragment. onAttachMetoda bierze contextzamiast activityjako parametr teraz.
Stan Mots

YesNoListenerJednak prawdopodobnie nie ma takiej potrzeby . Zobacz tę odpowiedź .
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
Dla kogo mój kod nie jest przydatny, wypróbuj go przed kliknięciem :-)
Chung IW,

6
Działa, nie wiem jak, ale działa! Czyste proste i abstrakcyjne rozwiązanie, dzięki.
nmvictor

3
Myślę, że ten kod jest zły. doLogout () ma odniesienie do kontekstu, który jest / zawiera aktywność. Aktywności nie można zniszczyć, co może spowodować wyciek pamięci. Szukałem możliwości użycia AlertDialog z kontekstu statycznego, ale teraz jestem pewien, że jest to niemożliwe. Myślę, że rezultatem mogą być tylko śmieci.
Niesamowity styczeń

2
Po prostu wygląda na to, że działa. Okno dialogowe pozostaje otwarte, ale nie ma połączenia z nowo utworzonym działaniem lub fragmentem (jest ono tworzone na nowo przy każdej zmianie orientacji). Nie możesz więc zrobić niczego, co wymaga umieszczenia Contextwewnątrz przycisków dialogowych OnClickListener.
Udo Klimaschewski

2
Ten kod działa, ale wcale nie jest zalecany. Wycieka odniesienie do aktywności, dlatego okno dialogowe może się utrzymywać. Jest to bardzo zła praktyka, która prowadzi do wycieku pamięci.
Hexise

4

Jeśli zmieniasz układ przy zmianie orientacji, nie umieszczałbym android:configChanges="orientation"w twoim manifeście, ponieważ i tak odtwarzasz widoki.

Zapisz aktualny stan swojej aktywności (np. Wprowadzony tekst, wyświetlone okno dialogowe, wyświetlone dane itp.), Korzystając z następujących metod:

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

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

W ten sposób działanie przechodzi ponownie przez onCreate, a następnie wywołuje metodę onRestoreInstanceState, w której można ponownie ustawić wartość EditText.

Jeśli chcesz przechowywać bardziej złożone obiekty, możesz użyć

@Override
public Object onRetainNonConfigurationInstance() {
}

Tutaj możesz przechowywać dowolny obiekt, aw onCreate wystarczy wywołać, getLastNonConfigurationInstance();aby uzyskać Object.


4
OnRetainNonConfigurationInstance()jest teraz przestarzały, jak mówi dokument: developer.android.com/reference/android/app/… setRetainInstance(boolean retain) należy zamiast tego użyć: developer.android.com/reference/android/app/…
ForceMagic

@ForceMagic setRetainInstancejest zupełnie inny: dotyczy fragmentów i nie gwarantuje, że instancja zostanie zachowana.
Miha_x64,

3

Po prostu dodaj android: configChanges = "orientacja" z elementem aktywności w AndroidManifest.xml

Przykład:

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

W niektórych przypadkach może to spowodować nieprawidłowe wyświetlanie okna dialogowego.
Sam

1
android: configChanges = "orientacja | screenSize" Uwaga: jeśli Twoja aplikacja jest przeznaczona dla systemu Android 3.2 (poziom interfejsu API 13) lub nowszego, należy również zadeklarować konfigurację „screenSize”, ponieważ zmienia się ona również, gdy urządzenie przełącza się między orientacją pionową i poziomą.
tsig

1

Bardzo prostym podejściem jest utworzenie okien dialogowych z metody onCreateDialog()(patrz uwaga poniżej). Pokazujesz im showDialog(). W ten sposób, Android obsługuje obrót dla Ciebie i nie trzeba zadzwonić dismiss()w onPause()celu uniknięcia WindowLeak i wtedy nie trzeba przywrócić okno. Z dokumentów:

Pokaż okno dialogowe zarządzane przez to działanie. Wywołanie onCreateDialog (int, Bundle) zostanie wykonane z tym samym identyfikatorem przy pierwszym wywołaniu tego identyfikatora. Od tego momentu okno dialogowe zostanie automatycznie zapisane i przywrócone.

Aby uzyskać więcej informacji, zobacz dokumentację dotyczącą systemu Android showDialog () . Mam nadzieję, że to komuś pomoże!

Uwaga: jeśli używasz AlertDialog.Builder, nie dzwoń show()z onCreateDialog(), create()zamiast tego dzwoń . Jeśli używasz ProgressDialog, po prostu utwórz obiekt, ustaw potrzebne parametry i zwróć go. Podsumowując, show()wnętrze onCreateDialog()powoduje problemy, po prostu stwórz instancję de Dialog i zwróć ją. To powinno działać! (Doświadczyłem problemów z używaniem showDialog () z onCreate () - właściwie nie pokazuję okna dialogowego - ale jeśli używasz go w onResume () lub w wywołaniu zwrotnym odbiornika, działa to dobrze).


W jakim przypadku potrzebujesz kodu? OnCreateDialog () lub pokazanie go za pomocą kreatora i wywołanie funkcji show ()?
Caumons

Udało mi się to zrobić ... ale chodzi o to, że onCreateDialog () jest teraz przestarzały: - \
neteinstein

OK! Pamiętaj, że większość urządzeń z Androidem nadal działa z wersjami 2.X, więc i tak możesz z niego korzystać! Zobacz wykorzystanie wersji platformy Android
Caumons

Jaka jest jednak inna opcja, jeśli nie naCreateDialog?
neteinstein

1
Możesz użyć klas budowniczych np AlertDialog.Builder. Jeśli użyjesz go wewnątrz onCreateDialog(), zamiast użyć, show()zwróć wynik create(). W przeciwnym razie wywołaj show()i zapisz zwrócony AlertDialog w atrybucie działania i w onPause() dismiss()nim, jeśli jest wyświetlany, aby uniknąć WindowLeak. Mam nadzieję, że to pomoże!
Caumons

1

Na to pytanie odpowiedziano dawno temu.

Jednak jest to niehackowe i proste rozwiązanie, którego używam dla siebie.

Zrobiłem tę klasę pomocniczą dla siebie, więc możesz jej użyć również w swojej aplikacji.

Wykorzystanie to:

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

Lub

 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

Możesz łączyć metody onSave / onRestore Dialog z metodami onSave / onRestore działania , aby zachować stan okna dialogowego.

Uwaga: ta metoda działa w przypadku „prostych” okien dialogowych, takich jak wyświetlanie komunikatu ostrzegawczego. Nie odtworzy zawartości WebView osadzonej w oknie dialogowym. Jeśli naprawdę chcesz zapobiec blokowaniu złożonych dialogów podczas rotacji, wypróbuj metodę 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

Zdecydowanie najlepszym podejściem jest użycie DialogFragment.

Oto moje rozwiązanie klasy wrapper, które pomaga zapobiegać odrzucaniu różnych okien dialogowych w jednym fragmencie (lub działaniu z małą refaktoryzacją). Pomaga to również uniknąć masowego refaktoryzacji kodu, jeśli z jakichś powodów jest dużo AlertDialogsrozproszonego kodu z niewielkimi różnicami między nimi pod względem działań, wyglądu lub czegoś innego.

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

Jeśli chodzi o aktywność, możesz wywołać ją w getContext()środku onCreateDialog(), przesłać ją do DialogProviderinterfejsu i zażądać określonego okna dialogowego przez mDialogId. Należy usunąć całą logikę postępowania z fragmentem docelowym.

Wykorzystanie z fragmentu:

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

Możesz przeczytać cały artykuł na moim blogu Jak zapobiec zamknięciu Dialogu? i baw się kodem źródłowym .


1

Wygląda na to, że jest to nadal problem, nawet jeśli „robisz wszystko dobrze” i używasz DialogFragmentitp.

W Google Issue Tracker istnieje wątek, który twierdzi, że jest to spowodowane pozostawieniem starej wiadomości o odrzuceniu w kolejce wiadomości. Podane obejście jest dość proste:

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

        super.onDestroyView();
    }

Niesamowite, że jest to nadal potrzebne 7 lat po pierwszym zgłoszeniu tego problemu.



0

Miałem podobny problem: kiedy zmieniła się orientacja ekranu, onDismisswywoływano detektor okna dialogowego, mimo że użytkownik nie zamknął okna. Udało mi się obejść ten problem, używając zamiast tego onCanceldetektora, który uruchamiał się zarówno wtedy, gdy użytkownik nacisnął przycisk Wstecz, jak i gdy użytkownik dotknął poza oknem dialogowym.


-3

Po prostu użyj

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

a aplikacja będzie wiedzieć, jak obsługiwać obrót i rozmiar ekranu.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.