Android Fragment反向堆栈的问题

我遇到了一个很大的问题,android片段backstack似乎工作的方式,将非常感激的任何帮助提供。

想象一下你有3个碎片

[1] [2] [3]

我希望用户能够导航[1] > [2] > [3]但是在返回的路上(按下后退button) [3] > [1]

正如我想象的那样,这可以通过在创build将fragment [2]带入XML中定义的片段持有者的事务时不调用addToBackStack(..)来实现。

这个现实似乎是,如果当用户按下[3]上的button时我不想再次出现,我不能在显示片段[3]的事务中调用addToBackStack 。 这似乎完全违反直觉(也许来自iOS世界)。

无论如何,如果我这样做的话,当我从[1] > [2]出发并按回时,如预期的那样回到[1]

如果我走[1] > [2] > [3] ,然后按回,我会跳回到[1] (如预期的那样)。 现在,当我尝试从[1]再次跳到[2]时,会出现奇怪的行为。 首先[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 heirarchybuild立的代码

  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] 

非常感谢

说明:这里发生了什么?

如果我们记住.replace().remove().add()我们知道的文档相同:

replace已添加到容器的现有片段。 这与为所有当前添加的片段调用remove(Fragment)相同的containerViewId ,然后使用此处给定的相同参数add(int, Fragment, String)相同。

那么发生的事情就是这样(我正在给frag添加数字,使之更加清晰):

 // 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 > 

可能的scheme

考虑实现FragmentManager.BackStackChangedListener来监视返回堆栈中的变化,并在onBackStackChanged()方法中应用你的逻辑:

  • 跟踪交易的次数;
  • 按名称检查特定事务FragmentTransaction.addToBackStack(String name);
  • 等等。

对!!! 拉了很多头发后,我终于弄清楚如何使这个工作正常。

看起来像片段[3]没有从视图中删除时,按下后,所以你必须手动做!

首先,不要使用replace(),而是使用remove和add来分开。 看起来好像replace()不能正常工作。

下一部分是覆盖onKeyDown方法,并在每次按下后退button时删除当前片段。

 @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(); } 

希望这可以帮助!

首先感谢@阿维斯眼睛开放的解释。

对于这个问题,我更喜欢不同的解决scheme。 我不喜欢乱搞重写后退行为超过绝对必要的,当我尝试添加和删除我自己的片段没有默认返回堆栈popup后button时,我发现我的自我在片段地狱:)如果你。当你删除f1时,将f2添加到f1中不会调用任何像onResume,onStart等callback方法,这可能是非常不幸的。

无论如何,这是我如何做到这一点:

目前显示的只是片段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事务方法被认为是asynchronous的,也许更好的方法是调用popBackStackImmediate()。 但是据我所知,在我的设备上它的工作是完美无缺的。

所述替代scheme将是:

 final FragmentActivity activity = getActivity(); activity.getSupportFragmentManager().popBackStackImmediate(); activity.getSupportFragmentManager().beginTransaction().replace(R.id.main_content, f3).addToBackStack(null).commit(); 

这里实际上会有短暂的回到f3,所以在那里出现一些小故障。

这实际上是所有你必须做的,不需要重写回栈的行为…

我知道这是一个老式的,但我得到了同样的问题,并像这样解决它:

首先,将Fragment1添加到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); 

希望它会帮助别人:)

在@阿维斯答复后,我决定深入挖掘,我写了一篇关于这方面的技术文章: http ://www.andreabaccega.com/blog/2015/08/16/how-to-avoid-fragments-overlapping- 由于到堆栈中,噩梦在-的Android /

对于周围的懒惰开发人员。 我的解决scheme是始终将事务添加到backstack中,并在需要时(自动)执行额外的FragmentManager.popBackStackImmediate() )。

代码是很less的代码行,在我的例子中,如果用户没有深入到后台(从C导航到D),我想从C跳到A而不跳回到“B”。

因此,附加的代码将按照A→B→C(后退)→A&A→B→C→D(后退)→C(后退)→B(后退)→A

哪里

 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()中的Activity中,通过标签查找fargment,然后做你的东西

 Fragment home = getSupportFragmentManager().findFragmentByTag("Home"); if (home instanceof HomeFragment && home.isVisible()) { // do you stuff } 

更多信息https://github.com/DattaHujare/NavigationDrawer我从来没有使用addToBackStack()来处理片段。;

我想,当我读到你的故事,[3]也在背后。 这解释了为什么你看到它闪烁。

解决方法是不要在堆栈上设置[3]。

我有一个类似的问题,我有同样的Activity [M1.F0] – > [M1.F1] – > [M1.F2] 3个连续的片段 ,然后调用一个新的Activity [M2]。 如果用户按下[M2]中的一个button,我想返回到[M1,F1],而不是[M1,F2],这是后退行为已经做的。

为了做到这一点,我删除[M1,F2],在[M1,F1]上调用显示,提交事务,然后通过隐藏调用返回[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(); 

嗨做完这段代码后:我不能看到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(); }