片段嵌套的问题

149

我想知道这是否实际上是Android API中的一个bug:

我的设置如下:

┌----┬---------┐
|    |         |
|  1 |    2    |
|    |┌-------┐|
|    ||       ||
|    ||   3   ||
└----┴┴-------┴┘
  1. 一个菜单,可以在右侧面板中加载片段2(搜索屏幕)。
  2. 一个搜索屏幕,包含了片段3,即结果列表。
  3. 结果列表在多个地方使用(包括作为自身的高级别片段)。

这个功能在手机上运行良好(其中1和2和3都是ActivityFragment)。

然而,当我使用了这段代码时:

    FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();       
    Fragment frag = new FragmentNumber2();
    if(toLoad != null) frag.setArguments(toLoad);
    transaction.replace(R.id.rightPane, frag);      
    transaction.commit();

在一个水平线性布局中,R.id.leftPaneR.id.rightPane<fragment>

据我所知,上述代码会移除驻留的片段,然后用一个新片段替换它。很出色...显然这不是发生的事情,因为当这段代码第二次运行时,你会得到以下异常:

07-27 15:22:55.940: ERROR/AndroidRuntime(8105): Caused by: java.lang.IllegalArgumentException: Binary XML file line #57: Duplicate id 0x7f080024, tag null, or parent id 0x0 with another fragment for FragmentNumber3

这是由于FragmentNumber3的容器被复制,且不再具有唯一的ID所导致的。在新的Fragment被添加之前,初始的Fragment没有被销毁(在我看来,这意味着它没有被替换)。

请问是否有可能实现这一点(此答案表明不可能),或者这是一个bug?


6
这是一个古老的问题,将其标记为重复问题有点无意义。 - pietv8x
6个回答

204

当前不支持嵌套片段。试图在另一个片段的UI中放置一个片段将导致未定义且可能破坏行为。

更新:自Android 4.2(和Android Support Library rev 11)以来,支持嵌套片段:http://developer.android.com/about/versions/android-4.2.html#NestedFragments

注意(根据此文档):“注意:当布局包括<fragment>时,不能将布局膨胀到片段中。仅在动态添加到片段时才支持嵌套片段。


14
不支持是因为这不是最初实现时的设计目标。我听到了很多关于该功能的请求,所以它可能会在某个时候完成,但通常有许多其他事情与之优先竞争。 - hackbod
4
我通过扩展FragmentActivity、FragmentManager和FragmentTransaction来实现这一点。基本原则是在我的活动中扩展DeferringFragmentActivity,并提供相同的API,因此不需要更改其他代码。当我调用getFragmentManager时,我获取一个DeferringFragmentManager实例,当我调用beginTransaction时,我获取一个DeferredTransaction。该事务存储了被调用方法和参数的POJOs。当调用提交(commit)时,我们首先查找任何待处理的DeferredTransactions。一旦所有事务都已提交,我们就启动一个真正的事务,并运行所有存储的方法及其参数。 - dskinner
11
那个时间点是现在。嵌套的“Fragment”现在已成为Android API的一部分,耶!http://developer.android.com/about/versions/android-4.2.html#NestedFragments - Alex Lockwood
9
哇,真是一场噩梦:如果你在Fragment上使用<fragment>,并且该Fragment恰好使用子Fragment,它不会清晰地失败(“不能将子Fragment添加到布局Fragment”),而是会以诸如“Fragment未创建视图”的异常神秘地失败。这样就浪费了好几个小时来调试…… - Glenn Maynard
6
@MartínMarconcini 当然,但根据API提供的功能,这并不明显。如果某些事情是不允许的,应该明确地记录下来,而不是让开发人员因为某些东西没有按照预期工作而抓狂。请你指导一下。 - dcow
显示剩余8条评论

98

Android 4.2及其以上版本支持嵌套片段。

现在,支持库也支持嵌套片段,因此您可以在Android 1.6及更高版本上实现嵌套片段设计。

要嵌套一个片段,只需在要添加片段的片段上调用getChildFragmentManager()。这将返回一个FragmentManager,您可以像从顶级活动一样使用它来创建片段事务。例如,以下是一些代码,它从现有Fragment类中添加一个Fragment:

Fragment videoFragment = new VideoPlayerFragment();
FragmentTransaction transaction = getChildFragmentManager().beginTransaction();
transaction.add(R.id.video_fragment, videoFragment).commit();

要了解关于嵌套片段的更多想法,请参阅以下教程:
Part 1
Part 2
Part 3

这里还有一个讨论有关嵌套片段的最佳实践的 Stack Overflow 帖子:best practices for nested fragments


嵌套片段的主要缺点是我们无法从子片段调用选项菜单 :( 如果我们使用ABS! - LOG_TAG
你能否帮我看一下这个问题?它非常相似。http://stackoverflow.com/questions/32240138/oncreateview-of-nested-fragment-is-not-called。对我来说,子片段无法从代码中膨胀。 - Nicks

34

..你可以在父Fragment的destroyView方法中清除嵌套Fragment:

@Override
    public void onDestroyView() {

      try{
        FragmentTransaction transaction = getSupportFragmentManager()
                .beginTransaction();

        transaction.remove(nestedFragment);

        transaction.commit();
      }catch(Exception e){
      }

        super.onDestroyView();
    }

4
如果你使用SetAlwaysFinish进行一些生命周期测试(http://bricolsoftconsulting.com/2011/12/23/how-to-test-onsaveinstancestate-and-onrestoreinstancestate-on-a-real-device/),你会发现当另一个启用了always finish的活动置于顶部时,此代码会导致错误(IllegalStateException: Can not perform this action after onSaveInstanceState)。将上述代码放入try/catch语句中并不是最优雅的解决方案,但似乎能使一切正常运行。 - Theo
这几乎起作用了。后来我在绘制UI时遇到了Stackoverflow。一定要避免嵌套片段... - neteinstein

14

我正在开发一个应用程序,它的布局类似于操作栏中的选项卡,可以启动碎片,其中一些碎片包含多个嵌入式碎片。

当我尝试运行此应用程序时,出现了相同的错误。如果在未选择选项卡后实例化xml布局中的Fragments,然后重新选择选项卡,我会收到填充器错误。

我解决了这个问题,用LinearLayout替换了xml中的所有碎片,然后使用Fragment Manager / Fragment Transaction来实例化碎片,目前在测试级别上一切正常。

希望这可以帮助您。


有人能评论一下这种方法的有效性吗?我觉得只能使用一个级别的片段很不幸 - 这样就不如不使用它们了。在占位符视图组上以编程方式添加它们是否可以无条件地工作? - Rafael Nobre
对我来说似乎仍然有效,我可以轻松地在视图持有者中交换它们。但需要注意的是,我只在Honeycomb上这样做,而没有兼容Ice Cream Sandwich。 - draksia

4

我曾经遇到过同样的问题,花了几天时间努力解决,最简单的方法是在选择/取消选择选项卡时使用fragment.hide() / fragment.show()。

public void onTabUnselected(ActionBar.Tab tab, FragmentTransaction ft)
{
    if (mFragment != null)
        ft.hide(mFragment);
}

当屏幕旋转发生时,所有父级和子级片段都会被正确销毁。
这种方法还有一个额外的优点——使用hide()/show()不会使片段视图失去它们的状态,因此,无需为ScrollView恢复先前的滚动位置。
问题在于,我不知道当它们不可见时是否正确不分离片段。我认为TabListener的官方示例是根据片段可重用并且不应该污染它们的内存而设计的,但是,如果您只有几个选项卡,并且您知道用户经常在它们之间切换,将它们附加到当前活动中将是合适的。
我想听取更有经验的开发人员的意见。

0
如果您发现嵌套的片段没有被删除或重复(例如在活动重新启动、屏幕旋转时),请尝试更改以下内容:
transaction.add(R.id.placeholder, newFragment);

transaction.replace(R.id.placeholder, newFragment);

如果以上方法无法解决问题,请尝试:
Fragment f = getChildFragmentManager().findFragmentById(R.id.placeholder);

FragmentTransaction transaction = getChildFragmentManager().beginTransaction();

if (f == null) {
    Log.d(TAG, "onCreateView: fragment doesn't exist");
    newFragment= new MyFragmentType();
    transaction.add(R.id.placeholder, newFragment);
} else {
    Log.d(TAG, "onCreateView: fragment already exists");
    transaction.replace(R.id.placeholder, f);
}
transaction.commit();

在这里学习


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