Запобігання вимкненню діалогового вікна при обертанні екрана в Android


94

Я намагаюсь запобігти вимкненню діалогових вікон, побудованих за допомогою конструктора сповіщень, під час перезапуску Діяльності.

Якщо я перевантажу метод onConfigurationChanged, я можу це успішно зробити і скинути макет до правильної орієнтації, але я втрачаю функцію липкого тексту в edittext. Отже, вирішуючи проблему діалогу, я створив цю проблему редагування тексту.

Якщо я зберігаю рядки з edittext і перепризначаю їх у зміні onCofiguration, вони все ще здаються типовими початковим значенням, а не тим, яке було введено до обертання. Навіть якщо я змушу інваліда, здається, оновити їх.

Мені дійсно потрібно вирішити або діалогову проблему, або проблему редагування тексту.

Дякую за допомогу.


1
Як зберегти / відновити вміст відредагованого EditText? Ви можете показати якийсь код?
Peter Knego

Я зрозумів проблему з цим, я забув отримати перегляд знову за допомогою Id після скидання макета.
draksia

Відповіді:


134

На сьогоднішній день найкращий спосіб уникнути цієї проблеми - це використання DialogFragment.

Створіть новий клас, який розширюється DialogFragment. Перевизначте onCreateDialogі поверніть свій старий Dialogабо AlertDialog.

Тоді ви можете показати це за допомогою DialogFragment.show(fragmentManager, tag).

Ось приклад із 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();
    }
}

І в Діяльності, яку ви телефонуєте:

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

Ця відповідь допомагає пояснити ці три інші запитання (та відповіді на них):


У моєму додатку є кнопка для виклику .show (), я повинен пам’ятати стан діалогового вікна попередження, яке відображається / відхиляється. Чи є спосіб зберегти діалогове вікно, не викликаючи .show ()?
Альфа Хуан

1
Там сказано, що onAttachзараз це застаріло. Що слід робити замість цього?
farahm

3
@faraz_ahmed_kamran, ви повинні використовувати onAttach(Context context)та android.support.v4.app.DialogFragment. onAttachМетод приймає contextзамість того , щоб в activityякості параметра в даний час.
Stan Mots

У цьому, мабуть, немає потреби YesNoListener. Дивіться цю відповідь .
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
Для кого мій код не корисний, спробуйте його перед натисканням :-)
Chung IW

6
Працює, не знаю як, але працює! Чисте просте та абстрактне рішення, дякую.
nmvictor

3
Я вважаю, що цей код поганий. doLogout () має посилання на контекст, який є / містить діяльність. Діяльність не можна знищити, що може спричинити витік пам'яті. Я шукав можливість використовувати AlertDialog із статичного контексту, але тепер я впевнений, що це неможливо. Результатом може бути лише сміття, я думаю.
Неймовірний

2
Це просто схоже на те, що це працює. Діалогове вікно залишається відкритим, але воно не має зв’язку з новоствореною діяльністю або фрагментом (воно створюється щоразу при кожній зміні орієнтації). Отже, ви не можете робити нічого, для чого потрібна Contextвнутрішня кнопка діалогового вікна OnClickListener.
Удо Клімашевський

2
Цей код працює, але взагалі не рекомендується. Це витікає посилання на активність, саме тому діалогове вікно може бути постійним. Це дуже погана практика, яка призведе до витоку пам'яті.
Hexise

4

Якщо ви змінюєте макет при зміні орієнтації, я не ставлю android:configChanges="orientation"у ваш маніфест, оскільки ви все одно відтворюєте подання.

Збережіть поточний стан своєї діяльності (наприклад, введений текст, показано діалогове вікно, відображені дані тощо), використовуючи такі методи:

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

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

Таким чином діяльність знову проходить через onCreate, а потім викликає метод onRestoreInstanceState, де ви можете знову встановити значення EditText.

Якщо ви хочете зберігати більш складні об'єкти, ви можете використовувати їх

@Override
public Object onRetainNonConfigurationInstance() {
}

Тут ви можете зберігати будь-який об'єкт, а в onCreate вам просто потрібно зателефонувати, getLastNonConfigurationInstance();щоб отримати об'єкт.


4
OnRetainNonConfigurationInstance()тепер засуджується як Док говорить: developer.android.com/reference/android/app / ... setRetainInstance(boolean retain) повинен використовувати замість: developer.android.com/reference/android/app / ...
ForceMagic

@ForceMagic setRetainInstance- це зовсім інше: це для фрагментів, і це не гарантує вам збереження екземпляра.
Miha_x64

3

Просто додайте android: configChanges = "direction" з вашим елементом активності в AndroidManifest.xml

Приклад:

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

Це може спричинити неправильне відображення діалогового вікна за певних обставин.
Сем

1
android: configChanges = "direction | screenSize" Примітка. Якщо ваша програма націлена на Android 3.2 (рівень API 13) або вище, вам слід також оголосити конфігурацію "screenSize", оскільки вона також змінюється, коли пристрій перемикається між портретною та альбомною орієнтацією.
tsig

1

Дуже простим підходом є створення діалогових вікон за методом onCreateDialog()(див. Примітку нижче). Ви показуєте їх наскрізь showDialog(). Таким чином, Android ручки обертання для вас , і вам не доведеться дзвонити dismiss()в , onPause()щоб уникнути WindowLeak , а потім ви ні повинні відновити діалог. З документів:

Показати діалогове вікно, яким керує ця діяльність. Виклик onCreateDialog (int, Bundle) буде здійснено з тим самим ідентифікатором, коли це вперше буде викликано для даного ідентифікатора. З цього часу діалогове вікно буде автоматично збережено та відновлено.

Для отримання додаткової інформації див. Документи Android showDialog () . Сподіваюся, це комусь допоможе!

Примітка: Якщо ви використовуєте AlertDialog.Builder, не дзвоніть show()з onCreateDialog(), зателефонуйте create()замість цього. Якщо ви використовуєте ProgressDialog, просто створіть об’єкт, встановіть потрібні параметри та поверніть його. На закінчення, show()всередині onCreateDialog()викликає проблеми, просто створіть екземпляр de Dialog і поверніть його. Це має спрацювати! (У мене виникли проблеми з використанням showDialog () від onCreate () - фактично не відображається діалогове вікно-, але якщо ви використовуєте його в onResume () або у зворотному виклику слухача, він працює добре).


Для якого випадку вам знадобиться якийсь код? OnCreateDialog () або показує його разом із конструктором і викликає до нього show ()?
Каумонс

Мені це вдалося зробити .. але справа в тому, що onCreateDialog () тепер застарілий: - \
neteinstein

ГАРАЗД! Майте на увазі, що більшість пристроїв Android все ще працюють з версіями 2.X, тож ви можете використовувати його в будь-якому випадку! Погляньте на використання версій платформи Android
Caumons

І все-таки, який інший варіант, якщо не onCreateDialog?
neteinstein

1
Ви можете використовувати класи конструктора, наприклад AlertDialog.Builder. Якщо ви використовуєте його всередині onCreateDialog(), замість використання show()повертайте результат create(). В іншому випадку викличте show()та збережіть повернутий AlertDialog в атрибут Activity і в onPause() dismiss()ньому, якщо він відображається, щоб уникнути WindowLeak. Сподіваюся, це допоможе!
Каумонс

1

На це запитання було дано відповідь давно.

Проте це невдале та просте рішення, яке я використовую для себе.

Я зробив цей допоміжний клас для себе, так що ви можете використовувати його і у своєму додатку.

Використання:

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

Або

 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

Ви можете поєднувати методи діалогу onSave / onRestore з методами onSave / onRestore Activity, щоб зберегти стан діалогу.

Примітка: Цей метод працює для тих "простих" діалогових вікон, таких як відображення попереджувального повідомлення. Він не відтворюватиме вміст WebView, вбудований у діалогове вікно. Якщо ви дійсно хочете попередити складне діалогове вікно під час обертання, спробуйте метод 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

Безумовно, найкращий підхід - це використання DialogFragment.

Ось моє рішення класу обгортки, яке допомагає запобігти відхиленню різних діалогів у межах одного Фрагменту (або Діяльності з невеликим рефакторингом). Крім того, це допомагає уникнути масової рефакторингу коду, якщо з якихось причин AlertDialogsсеред коду є багато розкиданих з незначними відмінностями між ними щодо дій, зовнішнього вигляду чи чогось іншого.

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

Коли справа доходить до Activity, ви можете викликати getContext()всередині onCreateDialog(), передати його в DialogProviderінтерфейс і запросити певне діалогове вікно за допомогою mDialogId. Вся логіка роботи з цільовим фрагментом повинна бути видалена.

Використання з фрагмента:

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

Ви можете прочитати повну статтю в моєму щоденнику Як запобігти відмові від діалогу? і пограйте з вихідним кодом .


1

Здається, це все ще проблема, навіть коли "все робиш правильно" та використовуєш DialogFragmentтощо.

У Google Issue Tracker є нитка, яка стверджує, що це пов’язано зі старим повідомленням про звільнення, яке залишається в черзі повідомлень. Наданий обхідний шлях досить простий:

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

        super.onDestroyView();
    }

Неймовірно, що це все ще потрібно через 7 років після першого повідомлення про цю проблему.



0

У мене була подібна проблема: коли орієнтація екрана змінилася, onDismissвикликався слухач діалогового вікна, хоча користувач не відхиляв діалогове вікно. Я зміг обійти це, замість цього використовуючи onCancelпрослуховувач, який спрацьовував як під час натискання користувачем кнопки назад, так і коли користувач торкався за межами діалогового вікна.


-3

Просто використовуйте

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

і програма знатиме, як обробляти обертання та розмір екрана.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.