Фрагменты, Диалоговое Фрагмент и Вращение экрана

У меня есть Activity, который вызывает setContentView с этим XML:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="horizontal"
    >
    <fragment android:name="org.vt.indiatab.GroupFragment"
        android:id="@+id/home_groups"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:layout_weight="1" />
            <..some other fragments ...>
</LinearLayout>

GroupFragment расширяет фрагмент, и все хорошо. Тем не менее, я показываю DialogFragment из GroupFragment. Это показывает правильно, ОДНАКО, когда экран вращается, я получаю Force Close.

Каким образом можно отобразить диалоговое окно Dialog из другого фрагмента, отличного от DialogFragment.show(FragmentManager, String)?

Ответ 1

ОК, в то время как метод Zsombor работает, это связано с тем, что я неопытен с Фрагментами, и его решение вызывает проблемы с saveInstanceState Bundle.

По-видимому (по крайней мере для DialogFragment), он должен быть public static class. Вы также должны написать свой собственный метод static DialogFragment newInstance(). Это связано с тем, что класс Fragment вызывает метод newInstance в своем методе instantiate().

Итак, в заключение, вы ДОЛЖНЫ писать свои диалоговые окна так:

public static class MyDialogFragment extends DialogFragment {

    static MyDialogFragment newInstance() {
        MyDialogFragment d = new MyDialogFragment();
        return d;
    }

    @Override
    public Dialog onCreateDialog(Bundle savedInstanceState) {
        ...
    }
}

И покажите им:

private void showMyDialog() {
    MyDialogFragment d = MyDialogFragment.newInstance();
    d.show(getFragmentManager(), "dialog");
}

Это может быть уникальным для библиотеки ActionBarSherlock, но официальные образцы в документации SDK также используют эту парадигму.

Ответ 2

В библиотеке совместимости есть ошибка, которая может вызвать это. Попробуйте поставить это в диалоговом окне:

@Override
public void onDestroyView() {
  if (getDialog() != null && getRetainInstance())
    getDialog().setOnDismissListener(null);
  super.onDestroyView();
}

Я также предлагаю установить ваш диалог, как сохраненный, поэтому он не будет уволен после поворота. Положите "setRetainInstance (true)"; например в методе onCreate().

Ответ 3

Чтобы превзойти Bundle, всегда являющийся нулевым, я сохраняю его в статическом поле в onSaveInstanceState. Это запах кода, но единственное решение, которое я нашел для восстановления диалога и сохранения состояния.

Ссылка Bundle должна быть нулевой в onDestroy.

@Override
public void onCreate(Bundle savedInstanceState)
{
    if (savedInstanceState == null)
        savedInstanceState = HackishSavedState.savedInstanceState;

    setRetainInstance(true);
}

@Override
public Dialog onCreateDialog(Bundle savedInstanceState)
{
    if (savedInstanceState == null)
        savedInstanceState = HackishSavedState.savedInstanceState;

    ...
}

@Override
public void onDestroyView() // necessary for restoring the dialog
{
    if (getDialog() != null && getRetainInstance())
        getDialog().setOnDismissListener(null);

    super.onDestroyView();
}

@Override
public void onSaveInstanceState(Bundle outState)
{
    ...

    HackishSavedState.savedInstanceState = outState;
    super.onSaveInstanceState(outState);
}

@Override
public void onDestroy()
{
    HackishSavedState.savedInstanceState = null;
    super.onDestroy();
}

private static class HackishSavedState
{
    static Bundle savedInstanceState;
}

Ответ 4

Я использовал сочетание представленных решений и добавил еще одну вещь. Это мое окончательное решение:

Я использовал setRetainInstance (true) в onCreateDialog; Я использовал это:

public void onDestroyView() {
    if (getDialog() != null && getRetainInstance())
        getDialog().setDismissMessage(null);
    super.onDestroyView();
}

И как обходной путь saveInstanceState не работает, я создал закрытый класс StateHolder (так же, как владелец создает для listView):

private class StateHolder {
    String name;
    String quantity;
}

Я сохраняю состояние следующим образом:

@Override
public void onSaveInstanceState(Bundle savedInstanceState) {
    super.onSaveInstanceState(savedInstanceState);
    stateHolder = new StateHolder();
    stateHolder.name = actvProductName.getText().toString();
    stateHolder.quantity = etProductQuantity.getText().toString();
}

В методе onDismiss я возвращаю stateHolder в значение null. Когда диалог создается, он проверяет, не является ли stateHolder нулевым, чтобы восстановить состояние или просто инициализировать все нормально.

Ответ 5

Я решил эту проблему с ответами @ZsomborErdődy-Nagy и @AndyDennie. Вы должны подклассифицировать этот класс и в своем родительском фрагменте вызовите setRetainInstance(true) и dialogFragment.show(getFragmentManager(), "Dialog");

 public class AbstractDialogFragment extends DialogFragment {

        @Override
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setRetainInstance(true);
        }

        @Override
        public void onDestroyView() {
            if (getDialog() != null && getRetainInstance())
                getDialog().setDismissMessage(null);
            super.onDestroyView();
        }
    }

Ответ 6

У меня была аналогичная проблема, но ни одно из выше сказанного не работало для меня. В итоге мне нужно было создать фрагмент в коде вместо XML-макета.

Смотрите: Замена фрагментов и изменение ориентации

Ответ 7

Я столкнулся с этим в своем проекте, и ни одно из вышеперечисленных решений не помогло.

Если исключение выглядит примерно как


java.lang.RuntimeException: Unable to start activity ComponentInfo{ 

...

        Caused by: java.lang.IllegalStateException: Fragment.... 
        did not create a view.

Это вызвано проблемой с резервным идентификатором контейнера, который используется после вращения. См. Этот билет для получения более подробной информации:

https://code.google.com/p/android/issues/detail?id=18529

В принципе, вы можете предотвратить крах, убедившись, что все ваши фрагменты xml имеют тег, определенный в макете. Это предотвращает возникновение условия резервного копирования, если вы вращаетесь, когда фрагмент виден.

В моем случае я смог применить это исправление без, чтобы переопределить onDestroyView() или setRetainInstance (true), что является общей рекомендацией для этой ситуации.

Ответ 8

Я столкнулся с этой проблемой, и трюк onDestroyView() не работал. Оказалось, что это произошло потому, что я делал довольно интенсивное диалоговое создание в onCreate(). Это включало сохранение ссылки на AlertDialog, которую я затем вернул бы в onCreateDialog().

Когда я переместил весь этот код в onCreateDialog() и остановил сохранение ссылки на диалог, он снова начал работать. Я ожидаю, что я нарушу один из инвариантов, который DialogFragment имеет об управлении своим диалогом.

Ответ 9

В onCreate() вызовите setRetainInstance(true), а затем включите это:

@Override
public void onDestroyView() {
    if (getDialog() != null && getRetainInstance()) {
        getDialog().setOnDismissMessage(null);
    }
    super.onDestroyView();
}

Когда вы вызываете setRetainInstance(true) в onCreate(), onCreate() больше не будет вызывать изменения ориентации, но onCreateView() все равно будет вызываться.

Итак, вы все равно можете сохранить состояние в своем пакете в onSaveInstanceState(), а затем вернуть его в onCreateView():

@Override
public void onSaveInstanceState(Bundle outState) {

    super.onSaveInstanceState(outState);

    outState.putInt("myInt", myInt);
}

@Override
public View onCreateView(LayoutInflater inflater, 
                         ViewGroup container, Bundle savedInstanceState) {

    View view = inflater.inflate(R.layout.my_layout, container);

    if (savedInstanceState != null) {

        myInt = savedInstanceState.getInt("myInt");
    }

    ...

    return view;
}