Как я могу поддерживать состояние fragmentа при добавлении в задний стек?

Я написал фиктивную активность, которая переключается между двумя fragmentами. Когда вы переходите из FragmentA в FragmentB, FragmentA добавляется в задний стек. Однако, когда я возвращаюсь к FragmentA (при нажатии назад), создается совершенно новый FragmentA, и состояние, в котором оно было, теряется. У меня такое чувство, что я повторяю то же самое, что и этот вопрос, но я включил полный образец кода, чтобы помочь устранить проблему:

public class FooActivity extends Activity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); final FragmentTransaction transaction = getFragmentManager().beginTransaction(); transaction.replace(android.R.id.content, new FragmentA()); transaction.commit(); } public void nextFragment() { final FragmentTransaction transaction = getFragmentManager().beginTransaction(); transaction.replace(android.R.id.content, new FragmentB()); transaction.addToBackStack(null); transaction.commit(); } public static class FragmentA extends Fragment { @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { final View main = inflater.inflate(R.layout.main, container, false); main.findViewById(R.id.next_fragment_button).setOnClickListener(new View.OnClickListener() { public void onClick(View v) { ((FooActivity) getActivity()).nextFragment(); } }); return main; } @Override public void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); // Save some state! } } public static class FragmentB extends Fragment { @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { return inflater.inflate(R.layout.b, container, false); } } } 

При добавлении некоторых сообщений журнала:

 07-05 14:28:59.722 D/OMG ( 1260): FooActivity.onCreate 07-05 14:28:59.742 D/OMG ( 1260): FragmentA.onCreateView 07-05 14:28:59.742 D/OMG ( 1260): FooActivity.onResume  07-05 14:29:12.842 D/OMG ( 1260): FooActivity.nextFragment 07-05 14:29:12.852 D/OMG ( 1260): FragmentB.onCreateView  07-05 14:29:16.792 D/OMG ( 1260): FragmentA.onCreateView 

Он никогда не вызывает FragmentA.onSaveInstanceState и создает новый FragmentA, когда вы ударяете назад. Однако, если я нахожусь на FragmentA, и я блокирую экран, FragmentA.onSaveInstanceState вызывает вызов. Настолько странно … Я ошибаюсь в ожидании fragmentа, добавленного в задний стек, чтобы не требовалось повторного создания? Вот что говорят документы :

Если при удалении fragmentа вы вызываете addToBackStack (), то fragment останавливается и будет возобновлен, если пользователь перейдет обратно.

Если вы вернетесь к fragmentу из заднего стека, он не воссоздает fragment, а повторно использует тот же экземпляр и запускается с onCreateView() в жизненном цикле fragmentа, см. Жизненный цикл fragmentа .

Поэтому, если вы хотите сохранить состояние, вы должны использовать переменные экземпляра и не полагаться на onSaveInstanceState() .

По сравнению с UINavigationController от Apple и UIViewController , Google не очень хорошо работает в архитектуре программного обеспечения Android. И документ Android о Fragment не очень помогает.

Когда вы вводите FragmentB из FragmentA, существующий экземпляр FragmentA не будет уничтожен. Когда вы нажимаете Назад в FragmentB и возвращаетесь к FragmentA, мы не создаем новый экземпляр FragmentA. Будет onCreateView() существующий экземпляр onCreateView() FragmentA.

Главное, мы не должны заново раздувать представление в onCreateView() FragmentA, потому что мы используем существующий экземпляр FragmentA. Нам нужно сохранить и повторно использовать rootView.

Следующий код работает хорошо. Он не только сохраняет состояние fragmentа, но также уменьшает нагрузку на ОЗУ и ЦП (потому что мы только раздуваем макет, если это необходимо). Я не могу поверить, что пример кода Google и документ никогда не упоминают об этом, но всегда раздувают макет .

Версия 1 (не использовать версию 1. Используйте версию 2)

 public class FragmentA extends Fragment { View _rootView; public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { if (_rootView == null) { // Inflate the layout for this fragment _rootView = inflater.inflate(R.layout.fragment_a, container, false); // Find and setup subviews _listView = (ListView)_rootView.findViewById(R.id.listView); ... } else { // Do not inflate the layout again. // The returned View of onCreateView will be added into the fragment. // However it is not allowed to be added twice even if the parent is same. // So we must remove _rootView from the existing parent view group // (it will be added back). ((ViewGroup)_rootView.getParent()).removeView(_rootView); } return _rootView; } } 

—— Обновление от 3 мая 2005 года: ——-

Как упоминалось в комментариях, иногда _rootView.getParent() имеет значение null в onCreateView , что приводит к сбою. Версия 2 удаляет _rootView в onDestroyView (), как предположил dell116. Протестировано на Android 4.0.3, 4.4.4, 5.1.0.

Версия 2

 public class FragmentA extends Fragment { View _rootView; public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { if (_rootView == null) { // Inflate the layout for this fragment _rootView = inflater.inflate(R.layout.fragment_a, container, false); // Find and setup subviews _listView = (ListView)_rootView.findViewById(R.id.listView); ... } else { // Do not inflate the layout again. // The returned View of onCreateView will be added into the fragment. // However it is not allowed to be added twice even if the parent is same. // So we must remove _rootView from the existing parent view group // in onDestroyView() (it will be added back). } return _rootView; } @Override public void onDestroyView() { if (_rootView.getParent() != null) { ((ViewGroup)_rootView.getParent()).removeView(_rootView); } super.onDestroyView(); } } 

ПРЕДУПРЕЖДЕНИЕ!!!

Это ХЭЙ! Хотя я использую его в своем приложении, вам нужно тщательно протестировать и прочитать комментарии.

Я думаю, есть альтернативный способ добиться того, что вы ищете. Я не говорю, что это полное решение, но оно служило цели в моем случае.

То, что я сделал, вместо замены fragmentа, я просто добавил fragment цели. Поэтому в основном вы будете использовать метод add() вместо replace() .

Что еще я сделал. Я скрываю свой текущий fragment, а также добавляю его в backstack.

Следовательно, он перекрывает новый fragment над текущим fragmentом, не разрушая его вид. (Убедитесь, что его onDestroyView() не вызывается. Добавляя его в backstate я backstate преимущество в возобновлении fragmentа.

Вот код:

 Fragment fragment=new DestinationFragment(); FragmentManager fragmentManager = getFragmentManager(); android.app.FragmentTransaction ft=fragmentManager.beginTransaction(); ft.add(R.id.content_frame, fragment); ft.hide(SourceFragment.this); ft.addToBackStack(SourceFragment.class.getName()); ft.commit(); 

Система onCreateView() вызывает вызовы onCreateView() если представление уничтожено или не создано. Но здесь мы сохранили представление, не удаляя его из памяти. Поэтому он не будет создавать новое представление.

И когда вы вернетесь из Destination Fragment, вы увидите последний FragmentTransaction удаляющий верхний fragment, который будет отображать верхний (SourceFragment) вид на экране.

КОММЕНТАРИЙ: Как я уже сказал, это не полное решение, так как оно не снимает вид fragmentа источника и, следовательно, занимает больше памяти, чем обычно. Но все равно служит цели. Также мы используем совершенно другой механизм скрытия вида вместо замены это не традиционное.

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

Я столкнулся с этой проблемой во Фрагменте, содержащем карту, в которой слишком много настроек для сохранения / перезагрузки. Мое решение состояло в том, чтобы в целом поддерживать этот Фрагмент активным все время (похоже на то, что упоминал @kaushal).

Скажем, у вас есть текущий fragment A и вы хотите отобразить fragment B. Обобщение последствий:

  • replace () – удалить fragment A и заменить его fragmentом B. Фрагмент A будет воссоздан после того, как он снова появится на переднем плане
  • add () – (create and) добавить fragment B и перекрывать fragment A, который все еще активен в фоновом режиме
  • remove () – можно использовать для удаления fragmentа B и возврата к A. Фрагмент B будет воссоздан при вызове позже

Следовательно, если вы хотите сохранить оба сохраненных fragmentа, просто переключите их с помощью функции hide () / show ().

Плюсы : простой и простой способ сохранения нескольких fragmentов
Минусы : вы используете намного больше памяти, чтобы все они работали. Может возникнуть проблема, например, отображение большого количества растровых изображений

onSaveInstanceState() вызывается только при изменении конфигурации.

Поскольку переход от одного fragmentа к другому отсутствует, изменение конфигурации не происходит, поэтому вызов onSaveInstanceState() не существует. Какое состояние не спасает? Можете ли вы указать?

Если вы введете какой-либо текст в EditText, он будет сохранен автоматически. Любой элемент пользовательского интерфейса без идентификатора – это элемент, состояние которого не сохраняется.

Здесь, поскольку onSaveInstanceState в fragmentе не вызывается, когда вы добавляете fragment в backstack. Жизненный цикл fragmentа в backstack при восстановлении начинается onCreateView и заканчивается onDestroyView а onSaveInstanceState вызывается между onDestroyView и onDestroy . Мое решение – создать переменную экземпляра экземпляра и init в onCreate . Образец кода:

 private boolean isDataLoading = true; private ArrayList listData; public void onCreate(Bundle savedInstanceState){ super.onCreate(savedInstanceState); isDataLoading = false; // init list at once when create fragment listData = new ArrayList(); } 

И проверьте его в onActivityCreated :

 public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); if(isDataLoading){ fetchData(); }else{ //get saved instance variable listData() } } private void fetchData(){ // do fetch data into listData } 
 getSupportFragmentManager().addOnBackStackChangedListener(new FragmentManager.OnBackStackChangedListener() { @Override public void onBackStackChanged() { if (getSupportFragmentManager().getBackStackEntryCount() == 0) { //setToolbarTitle("Main Activity"); } else { Log.e("fragment_replace11111", "replace"); } } }); YourActivity.java @Override public void onBackPressed() { Fragment fragment = getSupportFragmentManager().findFragmentById(R.id.Fragment_content); if (fragment instanceof YourFragmentName) { fragmentReplace(new HomeFragment(),"Home Fragment"); txt_toolbar_title.setText("Your Fragment"); } else{ super.onBackPressed(); } } public void fragmentReplace(Fragment fragment, String fragment_name) { try { fragmentTransaction = fragmentManager.beginTransaction(); fragmentTransaction.replace(R.id.Fragment_content, fragment, fragment_name); fragmentTransaction.setCustomAnimations(R.anim.enter_from_right, R.anim.exit_to_left, R.anim.enter_from_left, R.anim.exit_to_right); fragmentTransaction.addToBackStack(fragment_name); fragmentTransaction.commitAllowingStateLoss(); } catch (Exception e) { e.printStackTrace(); } } 

Моя проблема была похожа, но я победил меня, не сохранив fragment. Предположим, что у вас есть активность, в которой есть 2 fragmentа – F1 и F2. F1 запускается изначально и позволяет говорить, что содержит некоторую информацию о пользователе, а затем при некотором условии F2 появляется, запрашивая у пользователя дополнительный атрибут – их номер телефона. Затем вы хотите, чтобы этот номер телефона возвращался в F1 и завершал регистрацию, но вы понимаете, что все предыдущие данные пользователя потеряны, и у вас нет их предыдущих данных. Фрагмент воссоздается с нуля, и даже если вы сохранили эту информацию в onSaveInstanceState пакет возвращается обратно в onActivityCreated .

Решение. Сохраните требуемую информацию в качестве переменной экземпляра при вызове. Затем передайте эту переменную экземпляра в ваш fragment.

 @Override public void onActivityCreated(@Nullable Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); Bundle args = getArguments(); // this will be null the first time F1 is created. // it will be populated once you replace fragment and provide bundle data if (args != null) { if (args.get("your_info") != null) { // do what you want with restored information } } } 

Итак, следуйте моему примеру: перед отображением F2 я сохраняю данные пользователя в переменной экземпляра с помощью обратного вызова. Затем я запускаю F2, пользователь заполняет номер телефона и нажимает save. Я использую другой обратный вызов в действии, собираю эту информацию и заменяю свой fragment F1, на этот раз он имеет связанные данные, которые я могу использовать.

 @Override public void onPhoneAdded(String phone) { //replace fragment F1 f1 = new F1 (); Bundle args = new Bundle(); yourInfo.setPhone(phone); args.putSerializable("you_info", yourInfo); f1.setArguments(args); getFragmentManager().beginTransaction() .replace(R.id.fragmentContainer, f1).addToBackStack(null).commit(); } } 

Более подробную информацию о вызовах можно найти здесь: https://developer.android.com/training/basics/fragments/communicating.html

сначала : просто используйте метод add вместо метода classа FragmentTransaction, тогда вам нужно добавить secondFragment в стек методом addToBackStack

во-вторых : при обратном нажатии вы должны вызвать popBackStackImmediate ()

 Fragment sourceFragment = new SourceFragment (); final Fragment secondFragment = new SecondFragment(); final FragmentTransaction ft = getChildFragmentManager().beginTransaction(); ft.add(R.id.child_fragment_container, secondFragment ); ft.hide(sourceFragment ); ft.addToBackStack(NewsShow.class.getName()); ft.commit(); ((SecondFragment)secondFragment).backFragmentInstanceClick = new SecondFragment.backFragmentNewsResult() { @Override public void backFragmentNewsResult() { getChildFragmentManager().popBackStackImmediate(); } }; 

Замените fragment, используя следующий код:

 Fragment fragment = new AddPaymentFragment(); getSupportFragmentManager().beginTransaction().replace(R.id.frame, fragment, "Tag_AddPayment") .addToBackStack("Tag_AddPayment") .commit(); 

Активность onBackPressed ():

  @Override public void onBackPressed() { android.support.v4.app.FragmentManager fm = getSupportFragmentManager(); if (fm.getBackStackEntryCount() > 1) { fm.popBackStack(); } else { finish(); } Log.e("popping BACKSTRACK===> ",""+fm.getBackStackEntryCount()); } 
 Public void replaceFragment(Fragment mFragment, int id, String tag, boolean addToStack) { FragmentTransaction mTransaction = getSupportFragmentManager().beginTransaction(); mTransaction.replace(id, mFragment); hideKeyboard(); if (addToStack) { mTransaction.addToBackStack(tag); } mTransaction.commitAllowingStateLoss(); } replaceFragment(new Splash_Fragment(), R.id.container, null, false); 

Идеальное решение, которое находит старый fragment в стеке и загружает его, если оно существует в стеке.

 /** * replace or add fragment to the container * * @param fragment pass android.support.v4.app.Fragment * @param bundle pass your extra bundle if any * @param popBackStack if true it will clear back stack * @param findInStack if true it will load old fragment if found */ public void replaceFragment(Fragment fragment, @Nullable Bundle bundle, boolean popBackStack, boolean findInStack) { FragmentManager fm = getSupportFragmentManager(); FragmentTransaction ft = fm.beginTransaction(); String tag = fragment.getClass().getName(); Fragment parentFragment; if (findInStack && fm.findFragmentByTag(tag) != null) { parentFragment = fm.findFragmentByTag(tag); } else { parentFragment = fragment; } // if user passes the @bundle in not null, then can be added to the fragment if (bundle != null) parentFragment.setArguments(bundle); else parentFragment.setArguments(null); // this is for the very first fragment not to be added into the back stack. if (popBackStack) { fm.popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE); } else { ft.addToBackStack(parentFragment.getClass().getName() + ""); } ft.replace(R.id.contenedor_principal, parentFragment, tag); ft.commit(); fm.executePendingTransactions(); } 

использовать его как

 Fragment f = new YourFragment(); replaceFragment(f, null, boolean true, true); 
  • Как поместить список в намерение
  • Фильтровать LogCat, чтобы получать только сообщения из Моего приложения на Android?
  • Android в покупке приложения: проверка подписи не выполнена
  • Пикассо v / s Imageloader v / s Fresco vs Glide
  • Какова роль content_main.xml в android studio 1.4?
  • Android ViewPager с нижними точками
  • Как использовать обработчик Android для обновления TextView в streamе пользовательского интерфейса?
  • Ошибка библиотеки библиотеки материалов Google Тип программы уже присутствует: android.support.v4.app.INotificationSideChannel $ Stub $ Proxy
  • возможно ли скрыть системную панель
  • Можно ли разместить эти папки в другом месте?
  • Android - Как установить предпочтение в коде
  • Interesting Posts

    Как реализовать сайт с сотнями страниц в Angular2

    Страница результатов поиска Google на моем Firefox выглядит по-разному

    При загрузке папки происходит сбой Windows Explorer. Эта папка – мой рабочий стол

    Изменить громкость в Android?

    Извлечь максимальное значение в каждой группе в кадре данных

    Как получить информацию о принтере в .NET?

    Вырезать часть из видеофайла с начальной позиции до конечной позиции с помощью FFmpeg

    Две точки с запятой внутри круглых скобок

    Форматирование номеров страниц в оглавлении для включения текста

    MVC 4 как правильно передать данные из controllerа для просмотра

    Передача данных в макет, которые являются общими для всех страниц

    Подключение к гостевой системе VirtualBox (ubuntu) с хоста (osx) через NAT?

    Tmux замедляет процесс прерывания с помощью Ctrl-C

    Как показать условие «если» на диаграмме последовательности?

    Проблема десериализации с помощью DataContractJsonSerializer

    Давайте будем гением компьютера.