Android-фрагмент - Как сохранить состояния представлений во фрагменте, когда поверх него помещается другой фрагмент

В Android фрагмент (скажем, FragA) добавляется в стопку, а другой фрагмент (скажем, FragB) появляется наверху. Теперь при ответном ударе FragA выходит наверх, и вызывается onCreateView(). Теперь у меня было FragA в определенном состоянии, прежде чем FragB было нажато поверх него.

Мой вопрос: как я могу восстановить FragA в прежнее состояние? Есть ли способ сохранить состояние (например, в пакете), и если да, то какой метод я должен переопределить?


person pankajagarwal    schedule 22.07.2011    source источник


Ответы (12)


В руководстве по фрагментам пример FragmentList вы можете найти:

@Override
public void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);
    outState.putInt("curChoice", mCurCheckPosition);
}

Который вы можете использовать позже следующим образом:

@Override
public void onActivityCreated(Bundle savedInstanceState) {
    super.onActivityCreated(savedInstanceState);
    if (savedInstanceState != null) {
        // Restore last state for checked position.
        mCurCheckPosition = savedInstanceState.getInt("curChoice", 0);
    }
}

Я новичок в фрагментах, но это похоже на решение вашей проблемы;) OnActivityCreated вызывается после возврата фрагмента из заднего стека.

person ania    schedule 22.07.2011
comment
Я не мог заставить это работать, saveInstanceState всегда был нулевым. Я добавляю фрагмент через макет xml. Пришлось изменить mCurCheckPosition на static, тогда он работает, но кажется хакерским. - person scottyab; 10.07.2012
comment
@scottyab имеет ли ваш фрагмент идентификатор (если нет, то должен). - person ania; 10.07.2012
comment
Да, это так, я должен был отметить это в комментарии, в любом случае спасибо. - person scottyab; 10.07.2012
comment
он не вызывает onSaveInstanceState - с чего бы это? Итак, этот подход не работает. - person midnight; 29.12.2012
comment
@midnight, если это не так, вам следует искать причину, по которой она не вызывается, например называется "> stackoverflow.com/questions/8503629/ - person ania; 31.12.2012
comment
Будет ли этот подход действительно работать, если мы хотим сохранить состояние фрагмента при возврате из другого фрагмента в том же действии? onSaveInstanceState() вызывается только для событий Activity onPause/onStop. Согласно документации: также как и действие, вы можете сохранить состояние фрагмента с помощью Bundle на случай, если процесс действия будет убит, и вам нужно восстановить состояние фрагмента при воссоздании действия. Вы можете сохранить состояние во время обратного вызова фрагмента onSaveInstanceState() и восстановить его во время onCreate(), onCreateView() или onActivityCreated(). - person Paramvir Singh; 30.05.2013
comment
когда мы используем popbackstack(), тогда будет вызываться onSaveInstanceState(). Приведенный выше метод будет работать - person Sakthimuthiah; 14.06.2013
comment
Для справки, этот подход неверен и не должен иметь и близкого количества голосов, которые он имеет. onSaveInstanceState вызывается только тогда, когда соответствующая активность также закрывается. - person Martin Konecny; 30.08.2014
comment
onSaveInstanceState() вызывается только тогда, когда происходят изменения конфигурации и активность уничтожается, этот ответ неверен - person Tadas Valaitis; 05.01.2015
comment
Будет ли это работать, если я заменю фрагмент из контейнера добавлением этой транзакции в стопку? - person Gopal Singh Sirvi; 14.03.2015
comment
расскажите мне о переменной mCurCheckPosition? его тип данных? - person Faisal Ashraf; 20.05.2015
comment
Этот подход работает, потому что, когда вы возвращаетесь из другого фрагмента в рамках того же действия, элементы фрагмента все еще существуют, их не нужно восстанавливать. - person Boris Treukhov; 03.09.2015
comment
Это полностью сломано в Android и было долгое время. Я бы предложил создать собственную систему кэширования, используя сериализуемые методы onStop() и onStart() фрагмента. Это ясно, и вам не нужно иметь дело с дерьмом, которое представляет собой разработка для Android. - person Oliver Dixon; 25.02.2016

Фрагмент onSaveInstanceState(Bundle outState) никогда не будет вызываться, если только активность фрагмента не вызовет его для себя и прикрепленных фрагментов. Таким образом, этот метод не будет вызываться до тех пор, пока что-то (обычно вращение) не заставит активность SaveInstanceState и не восстановить ее позже. Но если у вас есть только одна активность и большой набор фрагментов внутри нее (с интенсивным использованием replace) и приложение работает только в одной ориентации, активность onSaveInstanceState(Bundle outState) может не вызываться в течение длительного времени.

Я знаю три возможных обходных пути.

Первое:

используйте аргументы фрагмента для хранения важных данных:

public class FragmentA extends Fragment {
    private static final String PERSISTENT_VARIABLE_BUNDLE_KEY = "persistentVariable";

    private EditText persistentVariableEdit;

    public FragmentA() {
        setArguments(new Bundle());
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment_a, null);

        persistentVariableEdit = (EditText) view.findViewById(R.id.editText);

        TextView proofTextView = (TextView) view.findViewById(R.id.textView);

        Bundle mySavedInstanceState = getArguments();
        String persistentVariable = mySavedInstanceState.getString(PERSISTENT_VARIABLE_BUNDLE_KEY);

        proofTextView.setText(persistentVariable);


        view.findViewById(R.id.btnPushFragmentB).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                getFragmentManager()
                        .beginTransaction()
                        .replace(R.id.frameLayout, new FragmentB())
                        .addToBackStack(null)
                        .commit();
            }
        });

        return view;
    }

    @Override
    public void onPause() {
        super.onPause();
        String persistentVariable = persistentVariableEdit.getText().toString();

        getArguments().putString(PERSISTENT_VARIABLE_BUNDLE_KEY, persistentVariable);
    }
}

Второй, но менее педантичный способ — держать переменные в синглтонах

Третье - не replace() фрагментов, а add()/show()/hide() их.

person Fyodor Volchyok    schedule 31.07.2014
comment
Лучшее решение, когда Fragment.onSaveInstanceState() никогда не вызывали. Просто сохраните свои собственные данные в аргумент, включая элементы в виде списка или только их идентификаторы (если у вас есть другой централизованный менеджер данных). Нет необходимости сохранять позицию представления списка - она ​​была сохранена и восстановлена ​​автоматически. - person John Pang; 22.11.2014
comment
Я пытался использовать ваш пример в своем приложении, но это: String persistentVariable = mySavedInstanceState.getString(PERSISTENT_VARIABLE_BUNDLE_KEY); всегда null. В чем проблема? - person fragon; 07.03.2015
comment
Использование фрагмента getArguments() - это ОПРЕДЕЛЕННЫЙ путь, включая вложенные фрагменты в ViewPager. Я использую 1 действие и меняю местами множество фрагментов, и это работает отлично. Вот простой тест для проверки любого предложенного решения: 1) перейти от фрагмента A к фрагменту B; 2) дважды изменить ориентацию устройства; 3) нажать кнопку назад на устройстве. - person Andy H.; 07.08.2015
comment
Я пробовал первый подход, но он не работал для меня. getArguments() всегда возвращает null. Это также имеет смысл, потому что фрагмент заменяется, а в onCreate() вы устанавливаете новый Bundle, поэтому старый Bundle теряется. Что я упускаю и ошибаюсь ли я? - person Zvi; 31.03.2016
comment
@Zvi, я написал этот код 1,5 года назад и не помню всех деталей, но, насколько я помню, замена не воссоздает фрагменты, фрагмент воссоздается только в том случае, если вы создали новый экземпляр из своего кода. В этом случае, очевидно, вызывается конструктор и setArguments(new Bundle()); перезаписывает старый Bundle. Поэтому убедитесь, что вы создали фрагмент только один раз, а затем используйте этот экземпляр вместо того, чтобы каждый раз создавать новый. - person Fyodor Volchyok; 31.03.2016
comment
@Федор Волчок, так что это действительно похоже на третий вариант, но я использую new() в своем приложении. В итоге я использовал newInstance(), передавая данные из одного фрагмента в другой и обратно. - person Zvi; 31.03.2016
comment
Спасибо за этот ответ. - person saintjab; 30.11.2018
comment
Я думаю, что третий путь является лучшим. stackoverflow.com/a/51909216/7272120 - person J.Dragon; 31.12.2020

Просто обратите внимание, что если вы работаете с фрагментами с помощью ViewPager, это довольно просто. Вам нужно только вызвать этот метод: setOffscreenPageLimit().

Согласно документам:

Установите количество страниц, которые должны сохраняться по обе стороны от текущей страницы в иерархии представлений в состоянии простоя. Страницы за пределами этого ограничения будут воссозданы с адаптера при необходимости.

Похожая проблема здесь

person e_v_e    schedule 06.04.2013
comment
Это другое. setOffScreenPageLimit действует как кеш (имеется в виду, сколько страниц ViewPager должен обрабатывать в данный момент), но не используется для сохранения состояния фрагмента. - person Renaud Mathieu; 31.12.2013
comment
В моем случае это работало с setOffscreenPageLimit() - хотя фрагменты были уничтожены, состояние просмотра было сохранено и восстановлено. - person Davincho; 26.04.2014
comment
Спасибо, мне тоже помогло. - person Elijah; 02.10.2015
comment
Прошли годы, а это все так же актуально. Хотя на самом деле это не отвечает на вопрос, это решает проблему - person Supreme Dolphin; 05.06.2016

Просто раздуйте свой View один раз.

Пример следующий:

public class AFragment extends Fragment {

private View mRootView;
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    if(mRootView==null){
        mRootView = inflater.inflate(R.id.fragment_a, container, false);
        //......
    }
    return mRootView;
}

}

person Lym Zoy    schedule 11.10.2016
comment
также следует сохранять существующие фрагменты в массиве или что-то в этом роде - person Amir; 27.11.2016
comment
Я слышал, что сохранение ссылки на rootView фрагмента является плохой практикой - может привести к утечкам [необходима цитата]? - person giraffe.guru; 12.12.2017
comment
@giraffe.guru Fragment ссылается на свое корневое представление, не будет этого делать. В то время как ссылка некоторых корневых элементов GC будет, как и глобальное статическое свойство, переменной потока, отличной от пользовательского интерфейса. Экземпляр Fragment не является GC-root, поэтому он может быть удален сборщиком мусора. Как и его корневой вид. - person Lym Zoy; 13.12.2017
comment
Ты же мой день. - person Vijendra patidar; 14.02.2020
comment
Мило и просто. - person Rupam Das; 09.06.2020

Я работал с проблемой, очень похожей на эту. Поскольку я знал, что мне часто придется возвращаться к предыдущему фрагменту, я проверил, верен ли фрагмент .isAdded(), и если да, то вместо transaction.replace() я просто делаю transaction.show(). Это предотвращает повторное создание фрагмента, если он уже находится в стеке — сохранение состояния не требуется.

Fragment target = <my fragment>;
FragmentTransaction transaction = getFragmentManager().beginTransaction();
transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
if(target.isAdded()) {
    transaction.show(target);
} else {
    transaction.addToBackStack(button_id + "stack_item");
    transaction.replace(R.id.page_fragment, target);
}
transaction.commit();

Еще одна вещь, которую следует иметь в виду, заключается в том, что, хотя это сохраняет естественный порядок самих фрагментов, вам все же может потребоваться обрабатывать само действие, которое уничтожается и воссоздается при изменении ориентации (конфигурации). Чтобы обойти это в AndroidManifest.xml для вашего узла:

android:configChanges="orientation|screenSize"

В Android 3.0 и выше, по-видимому, требуется screenSize.

Удачи

person rmirabelle    schedule 26.10.2012
comment
transaction.addToBackStack(button_id + stack_item);//что делает эта строка. Что такое button_id здесь? - person raghu_3; 18.03.2014
comment
button_id — это просто выдуманная переменная. Строковый аргумент, передаваемый в addToBackStack, является просто необязательным именем для состояния обратного стека — вы можете установить для него значение null, если вы управляете только одним обратным стеком. - person rmirabelle; 18.03.2014
comment
, у меня проблема, похожая на эту, с фрагментами, не могли бы вы изучить ее - button-not-when-i-select-a-fragme" title="состояние фрагмента Android восстанавливается только при нажатии кнопки "Назад", а не при выборе фрагмента"> stackoverflow.com/questions/22468977/ - person raghu_3; 18.03.2014
comment
Никогда не добавляйте android:configChanges=everythinYouCanThinkOf|moreThingsYouFoundOnTheInternets в свой манифест. Вместо этого узнайте, как сохранять и восстанавливать состояние, например, здесь: speakerdeck. com/cyrilmottier/ - person Marcin Koziński; 17.06.2015
comment
И затем, как только вы узнаете, насколько безумно громоздким является сохранение состояния и что представленное решение решает проблему наиболее чисто в вашей конкретной ситуации, продолжайте и используйте его, не заботясь о отрицательных голосах тех, кто не согласен ;-) - person rmirabelle; 18.06.2015
comment
android:configChanges=orientation|screenSize это помогло мне - person SpyZip; 04.02.2016

Лучшее решение, которое я нашел, приведено ниже:

onSavedInstanceState(): всегда вызывается внутри фрагмента, когда активность собирается закрыть (переместить активность из одной в другую или изменить конфигурацию). Итак, если мы вызываем несколько фрагментов для одной и той же активности, мы должны использовать следующий подход:

Используйте OnDestroyView() фрагмента и сохраните весь объект внутри этого метода. Затем OnActivityCreated(): проверьте, является ли объект нулевым или нет (поскольку этот метод вызывается каждый раз). Теперь восстановите состояние объекта здесь.

Всегда работает!

person Aman Goel    schedule 08.02.2016

если вы обрабатываете изменения конфигурации в своей активности фрагмента, указанной в манифесте Android, как это

<activity
    android:name=".courses.posts.EditPostActivity"
    android:configChanges="keyboardHidden|orientation"
    android:screenOrientation="unspecified" />

тогда onSaveInstanceState фрагмента не будет вызываться, а объект savedInstanceState всегда будет нулевым.

person Brook Oldre    schedule 10.07.2012

я не думаю, что onSaveInstanceState это хорошее решение. он просто используется для деятельности, которая была уничтожена.

Начиная с Android 3.0 Fragmen управляется FragmentManager, условие таково: одно действие, отображающее множество фрагментов, когда фрагмент добавляется (не заменяется: он будет воссоздан) в backStack, представление будет уничтожено. когда вернетесь к последнему, он будет отображаться как раньше.

Поэтому я думаю, что fragmentManger и транзакция достаточно хороши, чтобы справиться с этим.

person smallfatter    schedule 20.04.2016

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

public void addFragment(Fragment currentFragment, Fragment targetFragment, String tag) {
    FragmentManager fragmentManager = getSupportFragmentManager();
    FragmentTransaction transaction = fragmentManager.beginTransaction();
    transaction.setCustomAnimations(0,0,0,0);
    transaction.hide(currentFragment);
    // use a fragment tag, so that later on we can find the currently displayed fragment
    transaction.add(R.id.frame_layout, targetFragment, tag)
            .addToBackStack(tag)
            .commit();
}

Я использую этот метод в своем фрагменте (содержащем представление списка) всякий раз, когда элемент списка щелкается/нажимается (и поэтому мне нужно запустить/отобразить фрагмент сведений):

FragmentManager fragmentManager = getActivity().getSupportFragmentManager();
SearchFragment currentFragment = (SearchFragment) fragmentManager.findFragmentByTag(getFragmentTags()[0]);
DetailsFragment detailsFragment = DetailsFragment.newInstance("some object containing some details");
((MainActivity) getActivity()).addFragment(currentFragment, detailsFragment, "Details");

getFragmentTags() возвращает массив строк, которые я использую в качестве тегов для разных фрагментов при добавлении нового фрагмента (см. метод transaction.add в методе addFragment выше).

Во фрагменте, содержащем представление списка, я делаю это в его методе onPause():

@Override
public void onPause() {
    // keep the list view's state in memory ("save" it) 
    // before adding a new fragment or replacing current fragment with a new one
    ListView lv =  (ListView) getActivity().findViewById(R.id.listView);
    mListViewState = lv.onSaveInstanceState();
    super.onPause();
}

Затем в onCreateView фрагмента (фактически в методе, который вызывается в onCreateView) восстанавливаю состояние:

// Restore previous state (including selected item index and scroll position)
if(mListViewState != null) {
    Log.d(TAG, "Restoring the listview's state.");
    lv.onRestoreInstanceState(mListViewState);
}
person Javad    schedule 29.06.2015

В конце концов, после того, как я попробовал многие из этих сложных решений, поскольку мне нужно было только сохранить/восстановить одно значение в моем фрагменте (содержимое EditText), и хотя это может быть не самое элегантное решение, создание SharedPreference и сохранение моего состояния у меня сработало

person VMMF    schedule 25.05.2016

Простой способ хранения значений полей в разных фрагментах активности.

Создайте экземпляры фрагментов и добавьте вместо замены и удаления

    FragA  fa= new FragA();
    FragB  fb= new FragB();
    FragC  fc= new FragB();
    fragmentManager = getSupportFragmentManager();
    fragmentTransaction = fragmentManager.beginTransaction();
    fragmentTransaction.add(R.id.fragmnt_container, fa);
    fragmentTransaction.add(R.id.fragmnt_container, fb);
    fragmentTransaction.add(R.id.fragmnt_container, fc);
    fragmentTransaction.show(fa);
    fragmentTransaction.hide(fb);
    fragmentTransaction.hide(fc);
    fragmentTransaction.commit();

Затем просто покажите и скройте фрагменты вместо того, чтобы снова добавлять и удалять их.

    fragmentTransaction = fragmentManager.beginTransaction();
    fragmentTransaction.hide(fa);
    fragmentTransaction.show(fb);
    fragmentTransaction.hide(fc);
    fragmentTransaction.commit()

;

person Sreejesh K Nair    schedule 08.01.2020

person    schedule
comment
Пожалуйста, рассмотрите возможность включения некоторой информации о вашем ответе, а не просто размещение кода. Мы пытаемся предоставлять не только «исправления», но и помогать людям учиться. Вы должны объяснить, что было не так в исходном коде, что вы сделали по-другому и почему ваши изменения сработали. - person Andrew Barber; 09.01.2015