Android碎片 - 如何从一个视图移动到另一个视图?

12

我可以先将一个 Fragment 添加到一个 View 中,然后将其“分离(detach)”,再将其“重新附加(re-attach)”到另一个 View 中吗?

在代码中,我想要实现以下功能:

fragOne one = new fragOne();
getSupportFragmentManager().beginTransaction()
        .add(R.id.left, one, "tag").commit();

getSupportFragmentManager().beginTransaction()
        .detach(one).commit();      // or .remove(), or .addToBackStack(null).remove()

getSupportFragmentManager().executePendingTransactions();

getSupportFragmentManager().beginTransaction()
        .add(R.id.right, one).commit();

但是它会报错:
04-05 13:28:03.492: E/AndroidRuntime(7195): java.lang.RuntimeException: Unable to start activity ComponentInfo{com.example.trybackstack/com.example.trybackstack.MainActivity}: java.lang.IllegalStateException: Can't change container ID of fragment fragOne{40523130 #0 id=0x7f080000 tag}: was 2131230720 now 2131230721

感谢您的帮助!

您面临此异常是因为在添加“片段一”时,对片段容器使用了两个不同的ID(即R.id.right和R.id.left)。请使用一个单一的ID用于容器。 - Karan_Rana
1
确实。但我想将该片段从左移动到右。 - midnite
在这种情况下,您需要先提交前一个事务,然后再开始一个新的事务,将相同的片段添加到不同的视图中。 - Karan_Rana
@midnite,你找到解决方案了吗? - Accollativo
@Accollativo:还没有。但我会查看Android的源代码并找到解决方案!(或者找出为什么不可能这样做)。我目前正在忙于另一个项目。同时,您也可以查看解决方案的源代码,并在下面发布它 :-) - midnite
显示剩余2条评论
6个回答

17

我遇到了同样的问题,但我发现我真正需要的是重新父化片段视图而不是片段本身,从而避免任何fragmentManager事务。

View vv = fragment.getView();
ViewGroup parent = (ViewGroup)vv.getParent();
parent.removeView(vv);
newparent.addView(vv, layoutParams);

1
这是最佳解决方案,因为视图层次结构不会被销毁和重建。 - Greg Ennis
1
是的,我完全同意,因为在大多数情况下容器都是一个简单的FrameLayout,这种方法避免了与FragmentManager和Fragment生命周期打交道。 - Hai Zhang
2
这对我没有用,因为我想在方向改变时将片段从一个父级移动到另一个父级。因此,在方向更改后,片段的宿主视图被销毁了。 - Nabster
兄弟,你太棒了。运行得非常顺畅。将此响应与 https://medium.com/cs-random-thoughts-on-tech/android-retain-webview-on-orientation-change-best-practices-be29fd7e56e3 结合起来,现在我有了一个 WebView,可以在应用程序中任意传送而不会失去状态...谢谢!! - ValdaXD

14

在尝试了所有类似问题的答案之后,我似乎找到了一个可行的方法。

首要问题 - 在尝试将片段添加到另一个容器之前,您必须先提交执行删除事务。感谢nave的答案

但这种方法并不总是有效。第二个问题是后退栈。它会阻止事务。

所以完整的代码对我来说看起来像:

manager.popBackStackImmediate(null, manager.POP_BACK_STACK_INCLUSIVE);
manager.beginTransaction().remove(detailFragment).commit();
manager.executePendingTransactions();
manager.beginTransaction()
    .replace(R.id.content, masterFragment, masterTag)
    .add(R.id.detail, detailFragment, activeTag)
    .commit();              

3
这会导致视图被销毁并重新创建,而不是保留原有状态。 - Greg Ennis
在某些情况下,这种方法可能不起作用。我使用 nave 原始答案中的顺序获得了更好的结果:首先删除片段,然后杀死返回堆栈,最后添加片段。 - Theo

9

我想你现在已经理解了这个问题,但我没有看到令人满意的答案。因此,我为那些可能在未来参考此内容的人发布这篇文章。

如果您想将一个片段从一个视图移动到另一个视图,请按照以下步骤操作:

android.app.FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
fragmentTransaction.remove(fragment1);
fragmentTransaction.commit();
fragmentManager.executePendingTransactions();
fragmentTransaction = fragmentManager.beginTransaction();
fragmentTransaction.add(R.id.containerToMoveTo, fragment1);
fragmentTransaction.commit();

这样你就不必复制片段了。

1
无法运行... 错误:java.lang.RuntimeException: 无法启动活动ComponentInfo: java.lang.IllegalStateException: 无法更改片段SubMenuFragment{419b9c60 #1 id=0x7f050036}的容器ID:先前为2131034166,现在为2131034167。 - Accollativo
1
实际上这是可以工作的。在Nexus7/2012/4.4和Galaxy SII/4.1.2上测试过了。我做错了什么?=) - mjollneer
1
看起来这只适用于空的后退堆栈。这是改进的代码:https://dev59.com/7WUo5IYBdhLWcg3wtRQx#21328919 - Yan.Yurkin
3
这会导致视图被销毁并重新创建,而不是保留 - Greg Ennis 41秒前编辑 - Greg Ennis

3
请检查解决方案,如果您想保存旧片段的状态,则需要创建相同片段的新实例并用旧片段的状态进行实例化。
 FragmentTransaction ft = mFragmentManager.beginTransaction();
    ft.remove(one);
    Fragment newInstance = fetchOldState(one);
    ft.add(R.id.right, newInstance);
    ft.commit();


//TO fetch the old state
    private Fragment fetchOldState(Fragment f)
        {
            try {
                Fragment.SavedState oldState= mFragmentManager.saveFragmentInstanceState(f);

                Fragment newInstance = f.getClass().newInstance();
                newInstance.setInitialSavedState(oldState);

                return newInstance;
            }
            catch (Exception e) // InstantiationException, IllegalAccessException
            {

            }
        }

1
感谢您的回复@Karan_Rana!您的方法有效。但实际上它是_复制_片段,而不是真正的_移动_它。我也发现有一些人关心这个问题。我不确定。但似乎对于一个Fragment,一旦它被添加,我们就不能“重新添加”它。目前我找到的最好的方法似乎是复制它。然而,这可能会导致潜在的问题:它的引用可能不再起作用。 - midnite

0

补充Yan. Yurkin的答案。确保在事务中没有应用任何转换,因为这些似乎会延迟片段的分离。


0

我也遇到了这个问题。有时候移动片段可以解决问题,但有时候你必须创建一个新的实例。似乎如果片段一直处于“isRemoving”状态,则移动片段无法正常工作。 同时,将片段放入后退栈中也会防止其被删除。


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