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