Проблемы с Android Fragment back stack
у меня есть огромная проблема с тем, как Android фрагмент backstack, кажется, работает и был бы очень благодарен за любую помощь, которая предлагается.
представьте, что у вас есть 3 фрагмента
[1] [2] [3]
я хочу, чтобы пользователь мог перейти [1] > [2] > [3] но на обратном пути (нажав кнопку Назад) [3] > [1].
как я и предполагал, это будет достигнуто, не называя addToBackStack(..) при создании транзакции, которая приносит фрагмент [2] в держатель фрагмента, определенный в XML.
реальность этого кажется, что если я не хочу [2] чтобы появиться снова, когда пользователь нажимает кнопку назад на [3], Я не должен называть addToBackStack в транзакции, которая показывает фрагмент [3]. Это кажется совершенно нелогичным (возможно, из мира iOS).
в любом случае, если я делаю это таким образом, когда я иду от [1] > [2] и нажмите назад я возвращаюсь в [1] как и ожидалось.
если я пойду [1] > [2] > [3] а затем нажмите назад я прыгаю обратно в [1] (как и ожидалось).
Теперь странное поведение происходит, когда я пытаюсь перейти к [2] снова [1]. В первую очередь [3] появляется перед [2] попадает в поле зрения. Если я нажму назад в этот момент [3] отображается, и если я нажму назад еще раз, приложение выйдет.
А вот макет xml-файла для моего основного деятельность:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical" >
<fragment
android:id="@+id/headerFragment"
android:layout_width="match_parent"
android:layout_height="wrap_content"
class="com.fragment_test.FragmentControls" >
<!-- Preview: layout=@layout/details -->
</fragment>
<FrameLayout
android:id="@+id/detailFragment"
android:layout_width="match_parent"
android:layout_height="fill_parent"
/>
обновление
Это код, который я использую для создания nav heirarchy
Fragment frag;
FragmentTransaction transaction;
//Create The first fragment [1], add it to the view, BUT Dont add the transaction to the backstack
frag = new Fragment1();
transaction = getSupportFragmentManager().beginTransaction();
transaction.replace(R.id.detailFragment, frag);
transaction.commit();
//Create the second [2] fragment, add it to the view and add the transaction that replaces the first fragment to the backstack
frag = new Fragment2();
transaction = getSupportFragmentManager().beginTransaction();
transaction.replace(R.id.detailFragment, frag);
transaction.addToBackStack(null);
transaction.commit();
//Create third fragment, Dont add this transaction to the backstack, because we dont want to go back to [2]
frag = new Fragment3();
transaction = getSupportFragmentManager().beginTransaction();
transaction.replace(R.id.detailFragment, frag);
transaction.commit();
//END OF SETUP CODE-------------------------
//NOW:
//Press back once and then issue the following code:
frag = new Fragment2();
transaction = getSupportFragmentManager().beginTransaction();
transaction.replace(R.id.detailFragment, frag);
transaction.addToBackStack(null);
transaction.commit();
//Now press back again and you end up at fragment [3] not [1]
большое спасибо
8 ответов:
пояснение: что здесь происходит?
если мы будем иметь в виду, что
.replace()равен.remove().add()что мы знаем документацией:заменить существующий фрагмент, который был добавлен в контейнер. Это по сути то же самое, что и вызов
remove(Fragment)для всех добавленных в настоящее время фрагментов, которые были добавлены с тем жеcontainerViewIdа тоadd(int, Fragment, String)С теми же аргументами, приведенными здесь.тогда то, что происходит, похоже на это (я добавление чисел к фрагменту, чтобы сделать его более ясным):
// transaction.replace(R.id.detailFragment, frag1); Transaction.remove(null).add(frag1) // frag1 on view // transaction.replace(R.id.detailFragment, frag2).addToBackStack(null); Transaction.remove(frag1).add(frag2).addToBackStack(null) // frag2 on view // transaction.replace(R.id.detailFragment, frag3); Transaction.remove(frag2).add(frag3) // frag3 on view(здесь все вводящие в заблуждение вещи начинают происходить)
помните, что
.addToBackStack()спасает только сделки не фрагмент как и сама! Так что теперь у нас естьfrag3на макете:< press back button > // System pops the back stack and find the following saved back entry to be reversed: // [Transaction.remove(frag1).add(frag2)] // so the system makes that transaction backward!!! // tries to remove frag2 (is not there, so it ignores) and re-add(frag1) // make notice that system doesn't realise that there's a frag3 and does nothing with it // so it still there attached to view Transaction.remove(null).add(frag1) //frag1, frag3 on view (OVERLAPPING) // transaction.replace(R.id.detailFragment, frag2).addToBackStack(null); Transaction.remove(frag3).add(frag2).addToBackStack(null) //frag2 on view < press back button > // system makes saved transaction backward Transaction.remove(frag2).add(frag3) //frag3 on view < press back button > // no more entries in BackStack < app exits >возможное решение
рассмотреть вопрос об осуществлении
FragmentManager.BackStackChangedListenerчтобы следить за изменениями в заднем стеке и применять свою логику вonBackStackChanged()methode:
- трассировка количества транзакций;
- Проверьте конкретную транзакцию по имени
FragmentTransaction.addToBackStack(String name);- Etc.
правильно!!! после того, как много волос тянет я, наконец, понял, как сделать эту работу правильно.
Кажется, что фрагмент [3] не удаляется из вида при нажатии назад, поэтому вам нужно сделать это вручную!
прежде всего, не используйте replace (), но вместо этого используйте remove и add отдельно. Похоже, что replace () не работает должным образом.
следующая часть к этому переопределяет метод onKeyDown и удаляет текущий фрагмент каждый раз, когда назад кнопка нажата.
@Override public boolean onKeyDown(int keyCode, KeyEvent event) { if (keyCode == KeyEvent.KEYCODE_BACK) { if (getSupportFragmentManager().getBackStackEntryCount() == 0) { this.finish(); return false; } else { getSupportFragmentManager().popBackStack(); removeCurrentFragment(); return false; } } return super.onKeyDown(keyCode, event); } public void removeCurrentFragment() { FragmentTransaction transaction = getSupportFragmentManager().beginTransaction(); Fragment currentFrag = getSupportFragmentManager().findFragmentById(R.id.detailFragment); String fragName = "NONE"; if (currentFrag!=null) fragName = currentFrag.getClass().getSimpleName(); if (currentFrag != null) transaction.remove(currentFrag); transaction.commit(); }надеюсь, что это помогает!
прежде всего спасибо @Arvis за объяснение открытия глаз.
Я предпочитаю другое решение принятого ответа здесь для этой проблемы. Мне не нравится возиться с переопределением обратного поведения больше, чем абсолютно необходимо, и когда я попытался добавить и удалить фрагменты самостоятельно без всплывающего стека по умолчанию при нажатии кнопки "назад", я нашел себя в фрагменте ада :) если вы .добавьте f2 поверх f1 при его удалении f1 не будет вызывать ни один из методов обратного вызова, таких как onResume, onStart etc. и это может быть очень печально.
во всяком случае, вот как я это делаю:
В настоящее время на дисплее отображается только фрагмент f1.
f1 - > f2
Fragment2 f2 = new Fragment2(); this.getActivity().getSupportFragmentManager().beginTransaction().replace(R.id.main_content,f2).addToBackStack(null).commit();ничего необычного здесь. Чем во фрагменте f2 этот код приведет вас к фрагменту f3.
f2 - > f3
Fragment3 f3 = new Fragment3(); getActivity().getSupportFragmentManager().popBackStack(); getActivity().getSupportFragmentManager().beginTransaction().replace(R.id.main_content, f3).addToBackStack(null).commit();Я не уверен, читая документы, если это должно работать, этот метод транзакции poping называется асинхронным, и, возможно, лучшим способом было бы вызвать popBackStackImmediate (). Но насколько я могу судить на своих устройствах, он работает безупречно.
упомянутая альтернатива будет:
final FragmentActivity activity = getActivity(); activity.getSupportFragmentManager().popBackStackImmediate(); activity.getSupportFragmentManager().beginTransaction().replace(R.id.main_content, f3).addToBackStack(null).commit();здесь на самом деле будет краткое возвращение к f1 beofre переход к f3, поэтому небольшой глюк там.
это на самом деле все, что вам нужно сделать, не нужно переопределять поведение заднего стека...
Я знаю, что это старый quetion, но я получил ту же проблему и исправить это так:
во-первых, добавьте Фрагмент1 в BackStack с именем (например, "Frag1"):
frag = new Fragment1(); transaction = getSupportFragmentManager().beginTransaction(); transaction.replace(R.id.detailFragment, frag); transaction.addToBackStack("Frag1"); transaction.commit();и затем, когда вы хотите вернуться к Fragment1 (даже после добавления 10 фрагментов над ним), просто вызовите popBackStackImmediate с именем:
getSupportFragmentManager().popBackStackImmediate("Frag1", 0);надеюсь, что это поможет кому-то :)
после ответа @Arvis я решил копнуть еще глубже, и я написал техническую статью об этом здесь: http://www.andreabaccega.com/blog/2015/08/16/how-to-avoid-fragments-overlapping-due-to-backstack-nightmare-in-android/
для ленивых разработчиков ПО. Мое решение состоит в том, чтобы всегда добавлять транзакции в backstack и выполнять дополнительный
FragmentManager.popBackStackImmediate()при необходимости (автоматически).код очень мало строк кода и, в моем примере, я хотел перейти от C к A, не прыгая обратно в "B", если пользователь не пошел глубже в backstack (ex от C переходит к D).
следовательно, прилагаемый код будет работать следующим образом A - > B - > C (назад) - > A & А -> б -> c -> Д (задняя) -> С (назад) -> Б (назад) - а
здесь
fm.beginTransaction().replace(R.id.content, new CFragment()).commit()были выпущены от "B" до "C", как в вопросе.
Ок,ок, вот код :)
public static void performNoBackStackTransaction(FragmentManager fragmentManager, String tag, Fragment fragment) { final int newBackStackLength = fragmentManager.getBackStackEntryCount() +1; fragmentManager.beginTransaction() .replace(R.id.content, fragment, tag) .addToBackStack(tag) .commit(); fragmentManager.addOnBackStackChangedListener(new FragmentManager.OnBackStackChangedListener() { @Override public void onBackStackChanged() { int nowCount = fragmentManager.getBackStackEntryCount(); if (newBackStackLength != nowCount) { // we don't really care if going back or forward. we already performed the logic here. fragmentManager.removeOnBackStackChangedListener(this); if ( newBackStackLength > nowCount ) { // user pressed back fragmentManager.popBackStackImmediate(); } } } }); }
Если вы боретесь с addToBackStack () & popBackStack (), то просто используйте
FragmentTransaction ft =getSupportFragmentManager().beginTransaction(); ft.replace(R.id.content_frame, new HomeFragment(), "Home"); ft.commit();`в вашей деятельности в OnBackPressed () узнайте fargment по тегу, а затем сделайте свой материал
Fragment home = getSupportFragmentManager().findFragmentByTag("Home"); if (home instanceof HomeFragment && home.isVisible()) { // do you stuff }для получения дополнительной информации https://github.com/DattaHujare/NavigationDrawer Я никогда не использую addToBackStack() для обработки фрагмента.
Я думаю, когда я читаю ваш рассказ, что [3] также находится на заднем плане. Это объясняет, почему вы видите, что он мигает.
решение было бы никогда не устанавливать [3] в стеке.
у меня была аналогичная проблема, где у меня было 3 подряд фрагментов в том же
Activity[M1.F0] - >[M1.F1] - >[M1.F2] с последующим вызовом новогоActivity[M2]. Если пользователь нажал кнопку в [M2], я хотел вернуться к [M1,F1] вместо [M1, F2], что уже было сделано при обратном нажатии.для этого я удаляю [M1, F2], вызываю show на [M1, F1], фиксирую транзакцию,а затем добавляю [M1, F2] обратно, вызывая ее с помощью hide. Это убрали еще пресс, который в противном случае остался бы позади.
// Remove [M1.F2] to avoid having an extra entry on back press when returning from M2 final FragmentTransaction ftA = fm.beginTransaction(); ftA.remove(M1F2Fragment); ftA.show(M1F1Fragment); ftA.commit(); final FragmentTransaction ftB = fm.beginTransaction(); ftB.hide(M1F2Fragment); ftB.commit();
Привет После выполнения этого кода: я не могу видеть значение Fragment2 при нажатии клавиши Back. Мой Код:
FragmentTransaction ft = fm.beginTransaction(); ft.add(R.id.frame, f1); ft.remove(f1); ft.add(R.id.frame, f2); ft.addToBackStack(null); ft.remove(f2); ft.add(R.id.frame, f3); ft.commit(); @Override public boolean onKeyDown(int keyCode, KeyEvent event){ if(keyCode == KeyEvent.KEYCODE_BACK){ Fragment currentFrag = getFragmentManager().findFragmentById(R.id.frame); FragmentTransaction transaction = getFragmentManager().beginTransaction(); if(currentFrag != null){ String name = currentFrag.getClass().getName(); } if(getFragmentManager().getBackStackEntryCount() == 0){ } else{ getFragmentManager().popBackStack(); removeCurrentFragment(); } } return super.onKeyDown(keyCode, event); } public void removeCurrentFragment() { FragmentTransaction transaction = getFragmentManager().beginTransaction(); Fragment currentFrag = getFragmentManager().findFragmentById(R.id.frame); if(currentFrag != null){ transaction.remove(currentFrag); } transaction.commit(); }
Comments