【问题标题】:How to squash fragments on the backstack which match by their fragment tag?如何在 backstack 上压缩与其片段标签匹配的片段?
【发布时间】:2016-01-22 18:30:21
【问题描述】:

我的 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] 被添加到 backstack。

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

因此,用户需要多次按下BACK按钮才能将实例从backstack中弹出来清空详情视图。

当现有fragmentfragmentTag 与新fragmentfragmentTag 匹配时,如何替换后台堆栈上的DetailsFragment 现有实例[Dx]

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

【问题讨论】:

  • 如果栈顶有相同的标签,你想只替换顶部的片段还是替换堆栈上具有相同标签的任何片段?
  • @Michael 如果有一组具有相同标签的片段又名。堆栈顶部的相同类型我想用传入的单个片段来减少/替换它们。
  • 没有简单的方法可以实现这一点。检查这个答案stackoverflow.com/a/22125826/1320616
  • 尝试查找当前的详细信息片段,先将其删除,然后将新的详细信息片段添加到同一容器中。

标签: android android-fragments back-stack fragmentmanager


【解决方案1】:

我不确定我是否正确理解了您的问题。如果您想用具有相同标签的单个片段替换具有相同标签的单个片段,则可以使用以下方法。

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

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

现在,如果您进行以下调用,您的 backstack 将只包含 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

【讨论】:

  • 答案中的代码示例工作正常,但您只是使用不正确。在从 backstack 弹出片段之后,在开始事务之前立即调用 sidePane.setVisibility(View.VISIBLE);executePendingTransactions() 是在弹出和推送片段时避免竞争条件所必需的。我们同步弹出片段,所以我们也需要同步推送它们。
  • 提交事务后可以显示视图。唯一需要的是在弹出片段后显示它。 Daniel 的解决方案不适用于具有不同标签的片段。它包含我在上一条评论中提到的竞争条件。因此,在使用它时,您最终可能会在 backstack 中有多个细节片段。
  • 您隐藏了onBackStackChanged() 中的视图。片段会同步弹出,因此调用此方法并隐藏刚刚显示的视图。
  • 很棒的发现(大部分代码我都没有写)。所以我可以将sidePane.setVisibility(View.VISIBLE); 移动到onBackStackChanged() - 刚刚测试过 - 似乎有效。
  • 稍微分析您的代码后,我可以假设您不需要所有这些复杂性来从堆栈中弹出细节片段。只需为所有细节片段使用单独的 backstack 名称并执行popBackStack(FragmentStack.DETAIL, FragmentManager.POP_BACK_STACK_INCLUSIVE);。在这种情况下不需要executePendingTransactions()
【解决方案2】:

您可以使用标签并在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();
        }
    }
}

【讨论】:

  • 您的 content_frame 是否与我的 detail_fragment_placeholder 匹配 - 如果是,请更新您的代码 sn-p 以便更容易理解。您的解决方案是否意味着DetailFragment 类的一个或多个 实例被表示为一个 回栈条目 - 就像在n:1 中一样?
  • @jjd 是的,content_frame 与您的 detail_fragment_placeholder id 匹配,您可以像现在一样使用 containerViewId。我会更新答案!至于它是如何工作的,它只会将相同类型的第一个 Fragment 添加到 backstack 中,所以现在无论您使用 DetailsFragment 进入多少层,如果您点击返回,您会在导航到 DetailsFragment 之前回到顶层第一次。
  • 这两种方法都不完全符合我的要求。您的第一种方法-单击项目1-显示详细信息1,单击项目2-显示详细信息2,单击BACK-仍显示详细信息2,单击BACK - 应用程序进入后台。 --- 您的第二种方法:单击项目 1 - 显示详细信息 1,单击项目 2 - 详细视图为空。
  • @JJD 这很奇怪,第一种方法对我来说效果很好。第二种方法我还没有测试过,有时间我看看能不能搞定。
  • 你可以自己测试here。运行./gradlew clean assembleCcc32c3Debug
【解决方案3】:

只需在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 事件中,检查项目的位置是否与已显示的当前项目不匹配。如果是,什么也不做。

【讨论】:

  • 您回答的代码与您在我的问题中可以找到的代码 sn-p 相同。此外,我没有单击同一个项目,但每个项目都与 DetailsFragment 类的一个实例相关联。
  • 也许我不明白你的问题。你的问题不是从后台检索某个片段吗?
  • 问题是“用户需要多次按BACK按钮”。
  • 如何使用fragmentManager.popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE);
  • 这将弹出所有返回堆栈条目。我只想弹出DetailsFragment 类的实例。
猜你喜欢
  • 2016-02-11
  • 1970-01-01
  • 2012-12-30
  • 2014-09-26
  • 1970-01-01
  • 1970-01-01
  • 2012-06-08
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多