如何压缩与片段标签匹配的后台片段?

How to squash fragments on the backstack which match by their fragment tag?

我的 Android 平板电脑应用的布局由项目列表和详细信息视图组成。选择列表项时,相关内容将显示在详细信息视图中。

+--------+-------------+ 
| Item 1 |             |
+--------+    Item     |
| Item 2 |   details   |
+--------+             |
| Item 3 |             |
+--------+-------------+

详细信息视图是一个 Fragment,它以编程方式膨胀为 FrameLayout 占位符:

<FrameLayout
    android:id="@+id/detail_fragment_placeholder"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

这里是Fragment操作:

getSupportFragmentManager()
    .beginTransaction()
    .replace(containerViewId, fragment, fragmentTag)
    .addToBackStack(backStackStateName)
    .commit();

当用户一个接一个地选择项目时,DetailsFragment 的多个实例 [Dx] 被添加到后台堆栈。

                [D3]
        [D2]    [D2]
[D1] -> [D1] -> [D1]

因此,用户需要多次按下BACK按钮才能从后台弹出实例以清空详细信息视图。

当现有的 fragmentfragmentTag 与新 fragment?

[D1] -> [D2] -> [D3]

您可以使用标签并在 onBackPressed() 中处理它,但我认为在构建返回堆栈时处理它会是一个更干净的解决方案。有选择地为每个 FragmentTransaction 添加到返回堆栈,并且仅在它是 DetailsFragment 的第一个实例时才添加到返回堆栈。

这是一个简单的例子,它可以防止任何给定的 Fragment 连续两次被添加到返回堆栈:

public void replaceFragment(Fragment frag) {
    FragmentManager fm = getSupportFragmentManager();

    if (fm != null){
        FragmentTransaction t = fm.beginTransaction();
        //you could also use containerViewId in place of R.id.detail_fragment_placeholder
        Fragment currentFrag = fm.findFragmentById(R.id.detail_fragment_placeholder);
        if (currentFrag != null && currentFrag.getClass().equals(frag.getClass())) {
            t.replace(R.id.detail_fragment_placeholder, frag).commit();
        } else {
            t.replace(R.id.detail_fragment_placeholder, frag).addToBackStack(null).commit();
        }
    }
}

直接在backstack上找到fragment并替换即可:

Fragment fragment = getSupportFragmentManager().findFragmentByTag("your tag");
FragmentTransaction transaction = fm.beginTransaction();
transaction.replace(R.id.fragment_container, fragment, fragment.getClass().getName());
transaction.addToBackStack("your tag");
transaction.commit();

并且在您的 OnClick 事件中,检查项目的位置是否与已显示的当前项目不匹配。如果是,什么也不做。

我不确定我是否正确理解了你的问题。如果你想用带有相同标签的单个片段替换来自后台堆栈顶部的带有某些标签的多个片段,那么你可以使用以下方法。

不使用标签来识别片段,而是为不同类型的片段设置不同的后台堆栈名称。您仍然可以使用片段标签,但它无助于解决这个特定问题。然后手动从后台堆栈中逐一删除片段,直到顶部有一个具有不同后台堆栈名称的片段或没有剩余片段。

  public void addFragment(final int containerViewId, final Fragment fragment,
      final String backStackName, final boolean replace) {
    final FragmentManager fragmentManager = getSupportFragmentManager();

    if (replace) {
      while (fragmentManager.getBackStackEntryCount() > 0) {
        final int last = fragmentManager.getBackStackEntryCount() - 1;
        final FragmentManager.BackStackEntry entry = 
            fragmentManager.getBackStackEntryAt(last);
        if (!TextUtils.equals(entry.getName(), backStackName)) {
          break;
        }

        fragmentManager.popBackStackImmediate();
      }
    }

    fragmentManager
        .beginTransaction()
        .replace(containerViewId, fragment)
        .addToBackStack(backStackName)
        .commit();
    fragmentManager.executePendingTransactions();
  }

现在,如果您进行以下调用,您的后台堆栈将仅包含 fragment1fragment4

addFragment(R.id.container, fragment1, "D2", false);
addFragment(R.id.container, fragment2, "D1", false);
addFragment(R.id.container, fragment3, "D1", false);
addFragment(R.id.container, fragment4, "D1", true);

更新:

在这种特殊情况下,以下代码就足够了:

getSupportFragmentManager().popBackStack(
    backStackStateName, FragmentManager.POP_BACK_STACK_INCLUSIVE);
getSupportFragmentManager()
    .beginTransaction()
    .replace(containerViewId, fragment, fragmentTag)
    .addToBackStack(backStackStateName)
    .commit();

https://github.com/tuxmobil/CampFahrplan/pull/148