Android Fragment back stack 的问题
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/12529499/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Problems with Android Fragment back stack
提问by Chris Birch
I've got a massive problem with the way the android fragment backstack seems to work and would be most grateful for any help that is offered.
我在 android fragment backstack 的工作方式上遇到了一个大问题,非常感谢提供的任何帮助。
Imagine you have 3 Fragments
假设你有 3 个片段
[1] [2] [3]
[1] [2] [3]
I want the user to be able to navigate [1] > [2] > [3]
but on the way back (pressing back button) [3] > [1]
.
我希望用户能够导航[1] > [2] > [3]
但在返回的路上(按返回按钮)[3] > [1]
。
As I would have imagined this would be accomplished by not calling addToBackStack(..)
when creating the transaction that brings fragment [2]
into the fragment holder defined in XML.
正如我所想象的那样,这将通过addToBackStack(..)
在创建将片段[2]
带入 XML 中定义的片段持有者的事务时不调用来实现。
The reality of this seems as though that if I dont want [2]
to appear again when user presses back button on [3]
, I must not call addToBackStack
in the transaction that shows fragment [3]
. This seems completely counter-intuitive (perhaps coming from the iOS world).
现实情况似乎是,如果我不想[2]
在用户按下后退按钮时再次出现[3]
,我就不能调用addToBackStack
显示片段的事务[3]
。这似乎完全违反直觉(可能来自 iOS 世界)。
Anyway if i do it this way, when I go from [1] > [2]
and press back I arrive back at [1]
as expected.
无论如何,如果我这样做,当我离开[1] > [2]
并按回时,我会按[1]
预期返回。
If I go [1] > [2] > [3]
and then press back I jump back to [1]
(as expected).
Now the strange behavior happens when I try and jump to [2]
again from [1]
. First of all [3]
is briefly displayed before [2]
comes into view. If I press back at this point [3]
is displayed, and if I press back once again the app exits.
如果我去[1] > [2] > [3]
然后按回我跳回[1]
(如预期的那样)。现在,当我尝试[2]
从[1]
. [3]
在[2]
进入视野之前首先是简要显示。如果我此时按回[3]
显示,如果我再次按回应用程序退出。
Can anyone help me to understand whats going on here?
谁能帮我了解这里发生了什么?
And here is the layout xml file for my main activity:
这是我的主要活动的布局 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"
/>
UpdateThis is the code I'm using to build by 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]
Many thanks
非常感谢
回答by Arvis
Explanation: on what's going on here?
说明:这里发生了什么?
If we keep in mind that .replace()
is equal with .remove().add()
that we know by the documentation:
如果我们记住这.replace()
与.remove().add()
我们通过文档知道的相同:
Replace an existing fragment that was added to a container. This is essentially the same as calling
remove(Fragment)
for all currently added fragments that were added with the samecontainerViewId
and thenadd(int, Fragment, String)
with the same arguments given here.
替换添加到容器中的现有片段。这本质上与调用
remove(Fragment)
所有当前添加的片段相同,这些片段添加了相同的参数containerViewId
,然后add(int, Fragment, String)
使用此处给出的相同参数。
then what's happening is like this (I'm adding numbers to the frag to make it more clear):
然后发生的事情是这样的(我正在向碎片中添加数字以使其更加清晰):
// 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
(here all misleading stuff starts to happen)
(这里开始发生所有误导性的事情)
Remember that .addToBackStack()
is saving only transactionnot the fragmentas itself! So now we have frag3
on the layout:
请记住,这.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 >
Possible solution
可能的解决方案
Consider implementing FragmentManager.BackStackChangedListener
to watch for changes in the back stack and apply your logic in onBackStackChanged()
methode:
考虑实施FragmentManager.BackStackChangedListener
以观察后台堆栈中的更改并在onBackStackChanged()
方法中应用您的逻辑:
- Trace a count of transaction;
- Check particular transaction by name
FragmentTransaction.addToBackStack(String name);
- Etc.
- 跟踪交易计数;
- 按名称检查特定交易
FragmentTransaction.addToBackStack(String name);
- 等等。
回答by Chris Birch
Right!!! after much hair pulling I've finally worked out how to make this work properly.
对!!!经过大量的头发拉扯,我终于想出了如何使其正常工作。
It seems as though fragment [3] is not removed from the view when back is pressed so you have to do it manually!
当按下后退时,似乎片段 [3] 并未从视图中删除,因此您必须手动执行此操作!
First of all, dont use replace() but instead use remove and add separately. It seems as though replace() doesnt work properly.
首先,不要使用replace(),而是分别使用remove 和add。似乎 replace() 不能正常工作。
The next part to this is overriding the onKeyDown method and remove the current fragment every time the back button is pressed.
下一部分是覆盖 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();
}
Hope this helps!
希望这可以帮助!
回答by Nemanja Kovacevic
First of all thanks @Arvis for an eye opening explanation.
首先感谢@Arvis 的开眼解释。
I prefer different solution to the accepted answer here for this problem. I don't like messing with overriding back behavior any more than absolutely necessary and when I've tried adding and removing fragments on my own without default back stack poping when back button is pressed I found my self in fragment hell :) If you .add f2 over f1 when you remove it f1 won't call any of callback methods like onResume, onStart etc. and that can be very unfortunate.
对于这个问题,我更喜欢这里接受的答案的不同解决方案。我不喜欢在绝对必要的情况下搞乱覆盖后退行为,当我尝试自行添加和删除片段而没有在按下后退按钮时弹出默认后退堆栈时,我发现自己陷入了片段地狱:) 如果你 .在删除 f1 时将 f2 添加到 f1 上 f1 不会调用任何回调方法,如 onResume、onStart 等,这可能非常不幸。
Anyhow this is how I do it:
无论如何,这就是我的做法:
Currently on display is only fragment f1.
目前展出的只有片段 f1。
f1 -> f2
f1 -> f2
Fragment2 f2 = new Fragment2();
this.getActivity().getSupportFragmentManager().beginTransaction().replace(R.id.main_content,f2).addToBackStack(null).commit();
nothing out of the ordinary here. Than in fragment f2 this code takes you to fragment f3.
这里没什么特别的。与片段 f2 相比,此代码将您带到片段 f3。
f2 -> f3
f2 -> f3
Fragment3 f3 = new Fragment3();
getActivity().getSupportFragmentManager().popBackStack();
getActivity().getSupportFragmentManager().beginTransaction().replace(R.id.main_content, f3).addToBackStack(null).commit();
I'm not sure by reading docs if this should work, this poping transaction method is said to be asynchronous, and maybe a better way would be to call popBackStackImmediate(). But as far I can tell on my devices it's working flawlessly.
通过阅读文档,我不确定这是否可行,据说这种弹出事务方法是异步的,也许更好的方法是调用 popBackStackImmediate()。但据我所知,它在我的设备上运行得非常完美。
The said alternative would be:
所述替代方案是:
final FragmentActivity activity = getActivity();
activity.getSupportFragmentManager().popBackStackImmediate();
activity.getSupportFragmentManager().beginTransaction().replace(R.id.main_content, f3).addToBackStack(null).commit();
Here there will actually be brief going back to f1 beofre moving on to f3, so a slight glitch there.
在这里,实际上会在进入 f3 之前短暂返回 f1,所以这里有一个小故障。
This is actually all you have to do, no need to override back stack behavior...
这实际上就是你所要做的,不需要覆盖返回堆栈行为......
回答by Shirane85
I know it's a old quetion but i got the same problem and fix it like this:
我知道这是一个老问题,但我遇到了同样的问题并像这样修复它:
First, Add Fragment1 to BackStack with a name (e.g "Frag1"):
首先,使用名称(例如“Frag1”)将 Fragment1 添加到 BackStack:
frag = new Fragment1();
transaction = getSupportFragmentManager().beginTransaction();
transaction.replace(R.id.detailFragment, frag);
transaction.addToBackStack("Frag1");
transaction.commit();
And then, Whenever you want to go back to Fragment1 (even after adding 10 fragments above it), just call popBackStackImmediate with the name:
然后,每当您想返回 Fragment1(即使在其上方添加 10 个片段之后),只需使用名称调用 popBackStackImmediate:
getSupportFragmentManager().popBackStackImmediate("Frag1", 0);
Hope it will help someone :)
希望它会帮助某人:)
回答by Andrea Baccega
After @Arvis reply i decided to dig even deeper and I've written a tech article about this here: http://www.andreabaccega.com/blog/2015/08/16/how-to-avoid-fragments-overlapping-due-to-backstack-nightmare-in-android/
在@Arvis 回复之后,我决定深入挖掘,并在这里写了一篇关于此的技术文章:http://www.andreabaccega.com/blog/2015/08/16/how-to-avoid-fragments-overlapping-由于backstack-nightmare-in-android/
For the lazy developers around. My solution consists in always adding the transactions to the backstack and perform an extra FragmentManager.popBackStackImmediate()
when needed (automatically).
对于周围懒惰的开发人员。我的解决方案包括始终将事务添加到后台堆栈并FragmentManager.popBackStackImmediate()
在需要时(自动)执行额外操作。
The code is very few lines of code and, in my example, I wanted to skip from C to A without jumping back to "B" if the user didn't went deeper in the backstack (ex from C navigates to D).
代码只有很少的几行代码,在我的示例中,如果用户没有在返回堆栈中更深入(例如从 C 导航到 D),我想从 C 跳到 A 而不跳回“B”。
Hence the code attached would work as follow A -> B -> C (back) -> A & A -> B -> C -> D (back) -> C (back) -> B (back) -> A
因此,附加的代码将如下工作 A -> B -> C(后退)-> A & A -> B -> C -> D(后退)-> C(后退)-> B(后退)-> A
where
在哪里
fm.beginTransaction().replace(R.id.content, new CFragment()).commit()
were issued from "B" to "C" as in the question.
如问题所示,从“B”到“C”发出。
Ok,Ok here is the code :)
好的,好的,这是代码:)
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();
}
}
}
});
}
回答by Dattu Hujare
If you are Struggling with addToBackStack() & popBackStack() then simply use
如果您正在努力使用 addToBackStack() 和 popBackStack() 那么只需使用
FragmentTransaction ft =getSupportFragmentManager().beginTransaction();
ft.replace(R.id.content_frame, new HomeFragment(), "Home");
ft.commit();`
In your Activity In OnBackPressed() find out fargment by tag and then do your stuff
在你的 Activity In OnBackPressed() 中找出标签,然后做你的事情
Fragment home = getSupportFragmentManager().findFragmentByTag("Home");
if (home instanceof HomeFragment && home.isVisible()) {
// do you stuff
}
For more Information https://github.com/DattaHujare/NavigationDrawerI never use addToBackStack() for handling fragment.
有关更多信息https://github.com/DattaHujare/NavigationDrawer我从不使用 addToBackStack() 来处理片段。
回答by tim4dev
executePendingTransactions()
, commitNow()
not worked (
executePendingTransactions()
,commitNow()
没有工作(
Worked in androidx (jetpack).
在 androidx (jetpack) 工作。
private final FragmentManager fragmentManager = getSupportFragmentManager();
public void removeFragment(FragmentTag tag) {
Fragment fragmentRemove = fragmentManager.findFragmentByTag(tag.toString());
if (fragmentRemove != null) {
fragmentManager.beginTransaction()
.remove(fragmentRemove)
.commit();
// fix by @Ogbe
fragmentManager.popBackStackImmediate(tag.toString(),
FragmentManager.POP_BACK_STACK_INCLUSIVE);
}
}
回答by I?aqui
I had a similar issue where I had 3 consecutive fragmentsin the same Activity
[M1.F0]->[M1.F1]->[M1.F2] followed by a call to a new Activity
[M2]. If the user pressed a button in [M2] I wanted to return to [M1,F1] instead of [M1,F2] which is what back press behavior already did.
我有一个类似的问题,我在同一个[M1.F0]->[M1.F1]->[M1.F2] 中有 3 个连续的片段,Activity
然后调用一个新的Activity
[M2]。如果用户按下 [M2] 中的按钮,我想返回 [M1,F1] 而不是 [M1,F2],这是回按行为已经做过的。
In order to accomplish this I remove [M1,F2], call show on [M1,F1], commit the transaction, and then add [M1,F2] back by calling it with hide. This removed the extra back press that would have otherwise been left behind.
为了实现这一点,我删除了 [M1,F2],在 [M1,F1] 上调用 show,提交事务,然后通过使用 hide 调用它来添加回 [M1,F2]。这消除了否则会留下的额外背压。
// 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();
Hi After doing this code: I'm not able to see value of Fragment2 on pressing Back Key. My Code:
嗨,执行此代码后:我无法在按返回键时看到 Fragment2 的值。我的代码:
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();
}
回答by jdekeij
I think, when I read your story that [3] is also on the backstack. This explains why you see it flashing up.
我想,当我读到你的故事时,[3] 也在后台。这解释了为什么您看到它闪烁。
Solution would be to never set [3] on the stack.
解决方案是永远不要在堆栈上设置 [3]。