Android Fragment返回堆栈的问题

Chr*_*rch 119 android android-fragments back-stack

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

想象一下,你有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"

        />
Run Code Online (Sandbox Code Playgroud)



更新 这是我用于通过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]
Run Code Online (Sandbox Code Playgroud)

非常感谢

Arv*_*vis 202

说明:这里发生了什么?

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

替换添加到容器的现有片段.这与调用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
Run Code Online (Sandbox Code Playgroud)

(这里所有误导性的东西都开始发生了)

请记住,.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 >
Run Code Online (Sandbox Code Playgroud)

可能解决方案

考虑实现FragmentManager.BackStackChangedListener以监视后台堆栈中的更改并在onBackStackChanged()方法中应用您的逻辑:

  • 避免使用背堆!它对整体效率没有任何帮助!使用普通的replace(),甚至每次想要导航时都更好地删除/添加! (4认同)
  • 非常感谢这是片段交易最有用的解释!节省了我几个小时的时间. (2认同)

Chr*_*rch 33

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

看起来好像片段[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();

}
Run Code Online (Sandbox Code Playgroud)

希望这可以帮助!


Nem*_*vic 16

首先要感谢@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();
Run Code Online (Sandbox Code Playgroud)

这里没什么特别的.在片段f2中,此代码将您带到片段f3.

f2 - > f3

Fragment3 f3 = new Fragment3();
getActivity().getSupportFragmentManager().popBackStack();
getActivity().getSupportFragmentManager().beginTransaction().replace(R.id.main_content, f3).addToBackStack(null).commit();
Run Code Online (Sandbox Code Playgroud)

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

所述替代方案将是:

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

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

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

  • 但是小故障,这是一个问题 (6认同)

Shi*_*e85 13

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

首先,使用名称将片段1添加到BackStack(例如"Frag1"):

frag = new Fragment1();

transaction = getSupportFragmentManager().beginTransaction();
transaction.replace(R.id.detailFragment, frag);
transaction.addToBackStack("Frag1");
transaction.commit();
Run Code Online (Sandbox Code Playgroud)

然后,每当你想回到Fragment1时(即使在它上面添加了10个片段之后),只需使用以下名称调用popBackStackImmediate:

getSupportFragmentManager().popBackStackImmediate("Frag1", 0);
Run Code Online (Sandbox Code Playgroud)

希望它会帮助别人:)


And*_*ega 5

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

对于懒惰的开发者来说.我的解决方案在于始终将事务添加到backstack并FragmentManager.popBackStackImmediate()在需要时(自动)执行额外的操作.

代码是非常少的代码行,在我的例子中,如果用户没有深入到Backstack(从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()
Run Code Online (Sandbox Code Playgroud)

如问题所示,从"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();
        }
      }
    }
  });
}
Run Code Online (Sandbox Code Playgroud)

  • 在这条线上崩溃 fragmentManager.popBackStackImmediate();error: java.lang.IllegalStateException: FragmentManager 已经在 com.example.myapplication.FragmentA$2.onBackStackChanged(FragmentA.java:43) 上执行事务 (2认同)