Android Fragment back stack的问题


121

我对android片段Backstack的工作方式遇到了一个很大的问题,对于提供的任何帮助将不胜感激。

假设您有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"

        />



更新 这是我通过导航继承​​构建的代码

    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]

非常感谢


但是当我从片段C向片段A下压时,片段是重叠的
。– Priyanka,

我有同样的问题,如何解决?
普里扬卡

请参阅我的回复..可能会对您有所帮助< stackoverflow.com/questions/14971780/… >
MD Khali,

Answers:


203

说明:这是怎么回事?

如果我们牢记这.replace()一点与.remove().add()文档中所知道的相同:

替换添加到容器中的现有片段。这本质上与调用remove(Fragment)所有当前添加的片段相同,这些片段在此处添加了相同的containerViewId然后add(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()仅保存事务而不保存片段本身!所以现在我们有了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()methode中应用您的逻辑:


很好的解释@arvis。但是,如何避免这种行为而又不求助于骇人听闻的方式,例如DexterMoon的方法或Nemanja的方法,使用popBackStack在播放过渡动画时显示Fragment?
momo

您可以实现@momo FragmentManager.BackStackChangedListener来监视对后堆栈的更改。使用onBackStackChanged()methode 监视您的所有交易并根据需要采取行动:例如。跟踪BackStack中的事务计数;通过名称(FragmentTransaction addToBackStack (String name))等检查特定交易
。– Arvis

谢谢回答。我实际上在今天早些时候尝试过,注册了侦听器并从onBackstackChange中删除了该片段。弹出过渡播放时,片段变成空白区域。我猜该方法是在弹出开始动画时触发的,而不是在结束时触发…
momo

4
避免使用后堆栈!它并不能真正提高整体效率!使用普通的replace(),甚至每次导航时最好删除/添加!
2014年

@Arvis您能帮我得到同样的问题吗....堆栈计数为0,但我的片段仍然可见吗?
Erum

33

对!!!经过多拉头发,我终于弄清楚了如何使其正常工作。

似乎在按下后退按钮时并未从视图中删除片段[3],因此您必须手动进行操作!

首先,不要使用replace(),而要分别使用remove和add。似乎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();

}

希望这可以帮助!


这行得通,但是由于重新加载了片段,因此无法在我的项目中使用!有什么建议?
TharakaNirmana 2014年

但是,如果我正在这样做。我越来越黑屏的时候我来从C到A回片段C.
Nigam公司Patro

我怀疑@Arvis的答案应该可以解决您的问题
克里斯·伯奇

16

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

对于此问题,我更喜欢采用其他解决方案,而不是此处接受的答案。我不喜欢完全超出绝对优先级别的行为,当我尝试自己添加和删除片段而没有按下默认默认堆栈时,按下返回按钮时,我发现自己陷入了片段地狱:)如果您。在删除f1时在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,因此那里出现了一些小故障。

这实际上是您要做的所有事情,无需覆盖后退堆栈行为...


6
但是小故障是个问题
Zyoo

这似乎不像监听BackStack的更改那样“骇人听闻”。谢谢。
2014年

13

我知道这是一个古老的问题,但我遇到了同样的问题,并按以下步骤解决:

首先,使用名称(例如“ Frag1”)将Fragment1添加到BackStack:

frag = new Fragment1();

transaction = getSupportFragmentManager().beginTransaction();
transaction.replace(R.id.detailFragment, frag);
transaction.addToBackStack("Frag1");
transaction.commit();

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

getSupportFragmentManager().popBackStackImmediate("Frag1", 0);

希望它可以帮助某人:)


1
好答案。我必须使用getSupportFragmentManager()。popBackStackImmediate(“ Frag1”,FragmentManager.POP_BACK_STACK_INCLUSIVE); 使其适用于我的用例
eliasbagley

1
我必须在哪里放置此代码????getSupportFragmentManager()。popBackStackImmediate(“ Frag1”,0); On MainActivty或onBackPress
pavel

它不起作用。:(这是我的代码,在我的情况片段Overlaping它打开片段A,但Frgment A是重叠的片段B。
普里

FragmentManagerfragmentManager = getActivity()。getSupportFragmentManager(); fragmentManager.popBackStack(FragmentA.class.getName(),FragmentManager.POP_BACK_STACK_INCLUSIVE); FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction(); FragmentE fragmentE =新的FragmentE(); fragmentTransaction.replace(R.id.fragment_content,fragmentE,fragmentE.getClass()。getName()); fragmentTransaction.commit();
普里扬卡

5

在@Arvis回复后,我决定进行更深入的研究,并在这里写了一篇关于此的技术文章:http ://www.andreabaccega.com/blog/2015/08/16/how-to-a-void-fragments-overlapping- 由于在Android中的Backstack噩梦/

对于周围的懒惰开发人员。我的解决方案包括始终将事务添加到后台,并FragmentManager.popBackStackImmediate()在需要时(自动)执行其他操作。

该代码是几行代码,在我的示例中,如果用户没有在后堆栈中深入了解(例如,从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();
        }
      }
    }
  });
}

1
越来越坠毁在这条线fragmentManager.popBackStackImmediate();错误:java.lang.IllegalStateException:FragmentManager已经在com.example.myapplication.FragmentA $ 2.onBackStackChanged(FragmentA.java:43)执行事务
普里

1

如果您在与addToBackStack()和popBackStack()挣扎,则只需使用

FragmentTransaction ft =getSupportFragmentManager().beginTransaction();
ft.replace(R.id.content_frame, new HomeFragment(), "Home");
ft.commit();`

在OnBackPressed()的Activity中,按标签查找细分,然后执行操作

Fragment home = getSupportFragmentManager().findFragmentByTag("Home");

if (home instanceof HomeFragment && home.isVisible()) {
    // do you stuff
}

有关更多信息, 参见https://github.com/DattaHujare/NavigationDrawer,我从不使用addToBackStack()处理片段。


0

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

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


您好jdekei,谢谢您的输入。问题是我看不到要在堆栈中添加[3]的位置。我添加了另一段代码(以编程方式)演示了我正在使用按钮进行的​​导航。
克里斯·伯奇

它也对我有帮助,但是我只使用您代码中的removeCurFragment和另一个片段检查器。替换方法对我来说很好,也许s old method issue but now it很好。谢谢
Viktor V.

0

我有一个类似的问题,在同一[M1.F0]-> [M1.F1]-> [M1.F2]中有3个连续的片段Activity然后调用新的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();

嗨,完成此代码后:按Back键后,我看不到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();
    }

0

executePendingTransactions()commitNow()不起作用(

在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 using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.