理解碎片的返回栈

13

因此,有一些类似的问题,但我没有找到一个解决这个问题的答案。官方Android文档看起来很直观,但当我用更复杂的工作流实现应用程序时,片段回退栈变得混乱,奇怪的事情开始发生。我为简单应用程序开发了一个框架,其思想是一个可以通过其片段访问的单个活动以启动其他片段。我是这样做的:

1- 我让我的主要活动实现了一个名为“FragmentDelegate”的接口。

public interface FragmentDelegate {
    public void startFragment(CCFragment fragment, boolean addToBackStack);
}

2- startFrargment方法的实现:

@Override
public void startFragment(CCFragment fragment, boolean addToBackStack) {

    FragmentManager fragmentManager = getSupportFragmentManager();

    fragment.setFragmentDelegate(this);
    FragmentTransaction fragmentTransaction = fragmentManager
            .beginTransaction();

    fragmentTransaction.setCustomAnimations(R.anim.slide_in_right,
            R.anim.slide_out_left, R.anim.slide_in_left,
            R.anim.slide_out_right);

    fragmentTransaction.replace(CONTENT_VIEW_ID, fragment,
            "callerFragmentClassName");
    if (addToBackStack)
        fragmentTransaction.addToBackStack("callerFragmentClassName");
    fragmentTransaction.commitAllowingStateLoss();

}

这个很酷的地方在于,我可以从任何片段调用:
mFragmentDelegate.startFragment(aNewFragment, addToBackStack);

好的,现在考虑以下情况:

我从一个初始的片段A开始我的活动。从片段A开始,我调用相机活动以获取结果。当结果到达时,我启动片段B(将A添加到后退栈)。从B开始,我启动片段C 而不 将B添加到后退栈中。因此,我们在后退栈中有这个:

[A] [C]

如果我按返回按钮,我会回到A。 如果我重复这个过程,后退栈就会混乱,当我按返回键时,它会一遍又一遍地带我回到C片段......

我知道这很难理解(对我来说更难解释,因为英语不是我的母语),但如果有人能够解释一下Android片段后退栈如何真正工作,或者提供某种应用程序框架,那就太好了。


你是否正在使用findFragmentByTag(...)来获取已经实例化的Fragment的实例?因为这听起来像是你有多个相同的Fragment实例。 - Steve Benett
你说得对,当我启动一个片段时,我实例化一个新的片段...但我需要这样做是因为该片段可能会接收参数并且不同。 - Sebastian Breit
您可以随时为片段实例设置新的参数。 - Steve Benett
是的,但这不是问题所在...问题是,后退堆栈的行为并不像应该的那样。我想了解它的行为,而不是找到一个解决方法 :) 无论如何,谢谢! - Sebastian Breit
点击这里查看答案,我相信你会了解如何使用Fragment返回栈。 - GvSharma
一个片段何时调用startFragment? - stan0
4个回答

21

说明:

当FragmentManager回滚保存的FragmentTransactions时(例如用户点击返回按钮或您在其中调用了某些弹出方法),它执行存储在保存的FragmentTransaction中的操作的相反操作。您可以在这里查看确切的代码,它非常简单。

在您的情况下,保存的操作类型是“replace”,因此撤消该操作意味着删除所有添加的Fragment实例并重新添加已删除的实例。因此,当您有像这样的片段堆栈:

[A] [B]

FragmentManager知道,它必须在弹出操作时删除[B] Fragment实例并添加[A]。问题是,您用[C]替换了[B]:

[A] [C]

不幸的是,保存的Fragment BackStack条目不知道[C]。因此,在弹出操作期间,FragmentManager将重新添加[A],不会找到[B],因此对其不做任何操作,并将[C]保留在原地。

解决方案

解决此问题的一种可能方法是使用子片段。 Fragment-A是顶级Fragment,但Fragment-B和Fragment-C是WrapperFragment的子片段。

基本上,当您转到Fragment-B时,您将Fragment-A替换为保存的事务中的WrapperFragment:

[A] [[B]]

之后,当用户导航到Fragment-C时,您只需将WrapperFragment的[B]替换为[C]:

[A] [[C]]

当用户按下返回按钮时,我们将正确返回到Fragment-A:

[A]

Demo

我组装了一个GitHub项目来演示这种技术。


谢谢,我会尝试使用你的解决方案。WrapperFragment看起来非常有前途 :) - Sebastian Breit
非常有帮助,可以理解确切的过程。 - Mohit Dixit

1
我无法理解你是如何一次次弹出 C 片段而没有整个操作过程的全貌,但以下是你关于后退栈的错误理解:
  • BackStack 实际上保存了整个 FragmentManager 的当前状态,而不仅仅是单个片段。也就是说,如果您在单个事务中执行多个操作并涉及多个片段,并调用 addToBackStack(),则将保存即将执行的所有片段的状态。

  • 当您调用 addToBackStack() 时,实际上是将下一个状态添加到后退栈中,也就是说,如果您在添加 A 片段时调用了 addToBackStack(),则实际上有一个带 B 状态和一个带 A 状态的状态在其后面 -- [A] [B],而不是 [A] [C]。

我建议您使用此代码并通过查看 FragmentManager 对象的状态来尝试所有操作:

    int count = fragmentManager.getBackStackEntryCount();
    fragmentTransaction.addToBackStack(String.valueOf(count));

这样您就能追踪应用程序中真正发生的事情。

0

首先要记住:

 .replace() = .remove().add()

一些可能的解决方案

  public void onBackPressed(){
  // here you have to remove your last fragment.
  super.onBackPressed();
}

///////////////////////////////////////////////////////////////////////////////////////////////

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

if (currentFrag != null)
    transaction.remove(currentFrag);

transaction.commit();
}

////////////////////////////////////////////////////////////////////////////////////////

如欲了解有关碎片回退堆栈的更多信息,请点击此处点击这里


0

这可能看起来不是一个解决方案,但我认为在这种情况下最好不要使用片段。一开始只有一个活动并根据需要替换片段似乎是个好主意,但当你尝试以这种方式构建应用程序时,事情会变得混乱(我已经经历过了,我是从经验中说话的)。 自己管理BackStack只是其中一个问题。随着你的进一步发展,你最终会得到一个活动,里面有很多不相关的代码,这一点也不酷!

我建议您为每个片段创建一个活动,并将片段放入其中。在某些情况下,您的活动仍然可以拥有多个片段(比如平板电脑或横向模式),而在其他情况下只有一个片段(比如纵向模式)。但是您不再需要使用replace方法。

活动可以有父活动,因此BackStack问题将以最佳方式得到解决,还有许多其他问题。 这只是基于我使用片段的经验提出的建议。请按照您认为最好的方式进行操作。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接