首页 文章

Android Fragment返回堆栈的问题

提问于
浏览
111

我对Android片段后台堆栈的工作方式存在很大问题,并且非常感谢所提供的任何帮助 .

想象一下,你有3个碎片

[1] [2] [3]

我希望用户能够导航 [1] > [2] > [3] 但在回来的路上(按后退按钮) [3] > [1] .

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

这样的现实似乎如果我不希望 [2] 在用户按下 [3] 上的按钮时再次出现,我不能在显示片段 [3] 的事务中调用 addToBackStack . 这似乎完全违反直觉(可能来自iOS世界) .

无论如何,如果我这样做,当我从 [1] > [2] 开始并按回来时,我按照预期返回 [1] .

如果我去 [1] > [2] > [3] 然后按回去我跳回 [1] (如预期的那样) . 现在,当我尝试从 [1] 再次跳转到 [2] 时,会发生奇怪的行为 . 首先在 [2] 进入视图之前简要显示 [3] . 如果我在此时按回 [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"

        />

Update 这是我用于通过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 回答

  • 32

    说明:这里发生了什么?

    如果我们记住 .replace() 与我们通过文档知道的 .remove().add() 相等:

    替换添加到容器的现有片段 . 这与使用相同的containerViewId添加的所有当前添加的片段调用remove(Fragment),然后使用此处给出的相同参数添加(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() 仅保存 transaction 而不是 fragment 本身!所以现在我们在布局上有 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()方法中应用您的逻辑:

  • 0

    对!!!经过多次拔毛后,我终于找到了如何使这项工作正常进行 .

    看起来好像片段[3]在按下背面时没有从视图中移除,所以你必须手动完成!

    首先,不要使用replace(),而是使用remove并单独添加 . 似乎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();
    
    }
    

    希望这可以帮助!

  • 13

    首先感谢@Arvis的开眼界解释 .

    对于这个问题,我更喜欢不同的解决方案 . 我不喜欢凌乱的反击行为,而不是绝对必要的当我尝试添加和删除我自己的片段没有默认后面的堆栈弹出按下后退按钮我发现我的自我片段地狱:)如果你 . 当你删除它时,在f1上添加f2 f1将不会调用任何回调方法,如onResume,onStart等,这可能是非常不幸的 .

    无论如何这就是我这样做的:

    目前显示的只是片段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();
    

    我不确定通过阅读文档,如果这应该工作,这个弹出的事务方法据说是异步的,也许更好的方法是调用popBackStackImmediate() . 但到目前为止,我可以在我的设备上告诉它它的工作完美无瑕 .

    所述替代方案将是:

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

    这里实际上会有短暂的回到f1 beofre继续前进到f3,所以那里有一个轻微的故障 .

    这实际上是你所要做的,不需要覆盖后台堆栈行为......

  • 5

    我知道这是一个古老的问题,但我遇到了同样的问题并修复如下:

    首先,使用名称将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);
    

    希望它会帮助别人:)

  • 192

    @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”如果用户没有更深入的后台堆栈(从C导航到D导航) .

    因此附加的代码将按照以下方式工作 - >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();
            }
          }
        }
      });
    }
    
  • 0

    如果您正在与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()来处理片段 .

  • 16

    我想,当我读到你的故事[3]也在后台时 . 这就解释了为什么你会看到它闪烁 .

    解决方案是永远不要在堆栈上设置[3] .

  • 1

    我有一个类似的问题,我在同一个 Activity [M1.F0] - > [M1.F1] - > [M1.F2]中连续3次 fragments 然后调用新的 Activity [M2] . 如果用户按下[M2]中的按钮,我想返回[M1,F1]而不是[M1,F2],这是背压行为已经做到的 .

    为了实现这个目的,我删除[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();
    

    嗨执行此代码后:按下返回键时,我无法看到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();
        }
    

相关问题