ChatGPT解决这个技术问题 Extra ChatGPT

Android Fragment 回栈的问题

我在 android 片段 backstack 的工作方式上遇到了一个大问题,并且非常感谢提供的任何帮助。

想象一下你有 3 个片段

[1] [2] [3]

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

正如我想象的那样,这将通过在创建将片段 [2] 带入 XML 中定义的片段持有者的事务时不调用 addToBackStack(..) 来完成。

实际情况似乎是,如果我不想在用户按下 [3] 上的返回按钮时再次出现 [2],我就不能在显示片段 [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"

        />

更新这是我用来通过 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]

非常感谢

但是片段是重叠的,当我从片段 C 回压到片段 A 时。
我有同样的问题,你如何解决这个问题?
参考我的回答.. 它可能对你有帮助 <stackoverflow.com/questions/14971780/…>

H
H. Pauwelyn

说明:这里发生了什么事?

如果我们记住 .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() 只保存 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() 方法中应用您的逻辑:

跟踪交易计数;

按名称检查特定事务 FragmentTransaction.addToBackStack(String name);

等等。


很好的解释@arvis。但是,我们如何才能避免这种行为,而无需求助于像 DexterMoon 中的那种 hacky 方法,或者 Nemanja 中使用 popBackStack 的那种在播放过渡动画时显示 Fragment 的方法?
@momo,您可能会实现 FragmentManager.BackStackChangedListener 来监视对后台堆栈的更改。使用 onBackStackChanged() 方法监控您的所有交易并根据需要采取行动:例如。跟踪 BackStack 中的事务计数;按名称 (FragmentTransaction addToBackStack (String name)) 等检查特定交易。
谢谢回答。实际上我今天早些时候尝试过,注册了监听器并从 onBackstackChange 中删除了片段。在弹出过渡播放时,片段变成了一个白色的空白区域。我猜这个方法是在弹出动画开始而不是结束时触发的......
避免使用后堆栈!它对整体效率并没有真正的帮助!每次要导航时都使用普通的 replace() 甚至更好的删除/添加!
@Arvis你能帮我解决同样的问题....堆栈计数为0但我的片段仍然可见吗?
N
Null

当按下返回时,似乎片段 [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();

}

这可行,但是由于片段被重新加载,因此不能在我的项目中使用!有什么建议么?
但是,如果我这样做。当我从 C 回到片段 C 的 A 时,我得到了空白屏幕。
我怀疑@Arvis 的回答应该可以解决您的问题
N
Nemanja Kovacevic

首先感谢@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();

在进入 f3 之前,这里实际上会短暂地回到 f1,所以那里有一个小故障。

这实际上就是您所要做的,无需覆盖回栈行为......


但是故障,这是个问题
与监听 BackStack 更改相比,这似乎不那么“骇人听闻”。谢谢。
S
Shirane85

我知道这是一个老问题,但我遇到了同样的问题并像这样解决它:

首先,使用名称(例如“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);

希望它会帮助某人:)


很好的答案。我不得不使用 getSupportFragmentManager().popBackStackImmediate("Frag1", FragmentManager.POP_BACK_STACK_INCLUSIVE);让它为我的用例工作
我必须把这段代码放在哪里???? getSupportFragmentManager().popBackStackImmediate("Frag1", 0);在 MainActivty 或 onBackPress 上
它不工作。 :( 这是我的代码,在我的情况下,片段重叠。它打开片段 A,但片段 A 与片段 B 重叠。
FragmentManager fragmentManager = getActivity().getSupportFragmentManager(); fragmentManager.popBackStack(FragmentA.class.getName(),FragmentManager.POP_BACK_STACK_INCLUSIVE); FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();片段E 片段E = 新片段E(); fragmentTransaction.replace(R.id.fragment_content, fragmentE,fragmentE.getClass().getName()); fragmentTransaction.commit();
A
Andrea Baccega

在@Arvis 回复之后,我决定更深入地挖掘,并在这里写了一篇关于此的技术文章:http://www.andreabaccega.com/blog/2015/08/16/how-to-avoid-fragments-overlapping-due-to-backstack-nightmare-in-android/

对于周围的懒惰开发人员。我的解决方案是始终将事务添加到后台堆栈,并在需要时(自动)执行额外的 FragmentManager.popBackStackImmediate()

该代码是非常少的代码行,在我的示例中,如果用户没有深入后台堆栈(例如从 C 导航到 D),我想从 C 跳到 A 而不会跳回“B”。

因此附加的代码将按如下方式工作 A -> B -> C (back) -> A & A -> B -> C -> D (back) -> C (back) -> B (back) -> 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();
        }
      }
    }
  });
}

在这一行发生崩溃 fragmentManager.popBackStackImmediate();error: java.lang.IllegalStateException: FragmentManager 已经在 com.example.myapplication.FragmentA$2.onBackStackChanged(FragmentA.java:43) 处执行事务
D
Dattu Hujare

如果您正在努力使用 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() 来处理片段。


W
Wesley Wiser

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

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


嗨 jdekei 感谢您的意见。问题是我看不到将 [3] 添加到后台堆栈的位置。我添加了另一段代码,它(以编程方式)准确地演示了我使用按钮执行的导航。
它也对我有帮助,但我只使用你代码中的 removeCurFragment 和另一个片段检查器。 Replace 方法对我很有效,可能是,这很好。s old method issue but now it谢谢
N
N J

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

t
tim4dev

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