Запретить закрытие диалогового окна при повороте экрана в Android

94

Я пытаюсь предотвратить закрытие диалогов, созданных с помощью построителя предупреждений, при перезапуске действия.

Если я перегружу метод onConfigurationChanged, я смогу успешно сделать это и сбросить макет для правильной ориентации, но я потеряю функцию липкого текста для edittext. Итак, при решении проблемы диалога я создал эту проблему edittext.

Если я сохраню строки из edittext и переназначу их в изменении onCofiguration, они все равно будут иметь начальное значение по умолчанию, а не то, что было введено до поворота. Даже если я принудительно аннулирую их, кажется, что они обновляются.

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

Спасибо за помощь.

драксия
источник
1
Как сохранить / восстановить содержимое отредактированного EditText? Вы можете показать код?
Петр Кнего
Я понял проблему с этим, я забыл снова получить представление по идентификатору после сброса макета.
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 ()?
Alpha Huang
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 06
3

Просто добавьте android: configChanges = "Ориентация" с элементом действия в AndroidManifest.xml.

Пример:

<activity
            android:name=".YourActivity"
            android:configChanges="orientation"
            android:label="@string/app_name"></activity>
СААМ
источник
В некоторых случаях это может привести к неправильному отображению диалогового окна.
Sam
1
android: configChanges = "Ориентация | 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 () или в обратном вызове слушателя, он работает хорошо).

Caumons
источник
В каком случае вам понадобится код? OnCreateDialog () или показывать его с помощью построителя и вызывать для него show ()?
Caumons
Мне удалось это сделать .. но дело в том, что onCreateDialog () теперь устарела: - \
neteinstein
ХОРОШО! Имейте в виду, что большинство устройств Android по-прежнему работают с версиями 2.X, так что вы все равно можете использовать их! Взгляните на использование версий платформы Android
Caumons
Тем не менее, какой еще вариант, если не onCreateDialog?
neteinstein
1
Вы можете использовать классы построителя, например AlertDialog.Builder. Если вы используете его внутри onCreateDialog(), вместо использования show()верните результат create(). В show()противном случае вызовите и сохраните возвращенный AlertDialog в атрибуте Activity и в onPause() dismiss()нем, если он отображается, чтобы избежать WindowLeak. Надеюсь, поможет!
Caumons
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 Dialog с методами 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.

Вот мое решение класса оболочки, которое помогает предотвратить отклонение различных диалогов в одном фрагменте (или Activity с небольшим рефакторингом). Кроме того, это помогает избежать массового рефакторинга кода, если по каким-либо причинам 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 лет после того, как об этой проблеме впервые было сообщено.

Магнус В
источник
См. Также stackoverflow.com/questions/14657490/… .
CoolMind
0

У меня была аналогичная проблема: при изменении ориентации экрана onDismissбыл вызван слушатель диалога, даже если пользователь не закрыл диалог. Мне удалось обойти это, вместо этого используя onCancelпрослушиватель, который запускался как при нажатии пользователем кнопки «Назад», так и при касании пользователя вне диалогового окна.

Сэм
источник
-3

Просто используйте

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

и приложение будет знать, как обрабатывать поворот и размер экрана.

пользователь3384694
источник