Android FragmentTab主机和片段内的片段

我有一个像这样的层次结构的app:

FragmentTabHost (Main Activity) - Fragment (tab 1 content - splitter view) - Fragment (lhs, list) - Framment (rhs, content view) - Fragment (tab 2 content) - Fragment (tab 2 content) 

所有片段视图都从资源中膨胀。

当应用程序启动时,一切都会显示,看起来很 当我从第一个选项卡切换到另一个选项卡并再次返回时,我会尝试重新启动exception,尝试重新创建选项卡1的视图。

深入挖掘,这就是发生的事情:

  • 在第一次加载时,对分割器视图进行膨胀会导致其两个子片段被添加到片段管理器中。
  • 在切换远离第一个选项卡时,它的视图被破坏但它的子片段留在片段管理器中
  • 切换回第一个选项卡时,视图会重新膨胀,并且由于旧的子片段仍在片段管理器中,因此在实例化新的子片段时会抛出exception(通过膨胀)

我通过从片段管理器中删除子片段来解决这个问题(我正在使用Mono),现在我可以切换标签而没有例外。

 public override void OnDestroyView() { var ft = FragmentManager.BeginTransaction(); ft.Remove(FragmentManager.FindFragmentById(Resource.Id.ListFragment)); ft.Remove(FragmentManager.FindFragmentById(Resource.Id.ContentFragment)); ft.Commit(); base.OnDestroyView(); } 

所以我有几个问题:

  1. 以上是正确的方法吗?
  2. 如果没有,我该怎么办呢?
  3. 无论哪种方式,保存实例状态如何与所有这些联系在一起,以便在切换选项卡时不会丢失视图状态?

我不确定如何在Mono中执行此操作,但是要将子片段添加到另一个片段,您不能使用ActivityFragmentManager 。 相反,您必须使用托管FragmentChildFragmentManager

http://developer.android.com/reference/android/app/Fragment.html#getChildFragmentManager() http://developer.android.com/reference/android/support/v4/app/Fragment.html#getChildFragmentManager()

Activity的主要FragmentManager处理您的选项卡。
tab1ChildFragmentManager处理拆分视图。

好的,我终于明白了:

如上所述,首先我将片段创建更改为以编程方式完成,并将它们添加到子片段管理器中,如下所示:

 public override View OnCreateView(LayoutInflater inflater, ViewGroup viewGroup, Bundle savedInstance) { var view = inflater.Inflate(Resource.Layout.MyView, viewGroup, false); // Add fragments to the child fragment manager // DONT DO THIS, SEE BELOW var tx = ChildFragmentManager.BeginTransaction(); tx.Add(Resource.Id.lhs_fragment_frame, new LhsFragment()); tx.Add(Resource.Id.rhs_fragment_frame, new RhsFragment()); tx.Commit(); return view; } 

正如预期的那样,每次切换标签时,都会创建一个额外的Lhs / RhsFragment实例,但我注意到旧的Lhs / RhsFragment的OnCreateView也会被调用。 因此,在每个选项卡切换后,将再有一次对OnCreateView的调用。 切换标签10次= 11次调用OnCreateView。 这显然是错误的。

查看FragmentTabHost的源代码,我可以看到它在切换选项卡时只是分离并重新附加选项卡的内容片段。 似乎父Fragment的ChildFragmentManager保持子片段并在重新附加父片段时自动重新创建它们的视图。

所以,我将片段的创建移动到OnCreate,并且只有当我们没有从保存状态加载时:

 public override void OnCreate(Bundle savedInstanceState) { base.OnCreate(savedInstanceState); if (savedInstanceState == null) { var tx = ChildFragmentManager.BeginTransaction(); tx.Add(Resource.Id.lhs_fragment_frame, new LhsFragment()); tx.Add(Resource.Id.rhs_fragment_frame, new RhsFragment()); tx.Commit(); } } public override View OnCreateView(LayoutInflater inflater, ViewGroup viewGroup, Bundle savedInstance) { // Don't instatiate child fragments here return inflater.Inflate(Resource.Layout.MyView, viewGroup, false); } 

这修复了附加视图的创建和切换选项卡现在基本上工作了。

接下来的问题是保存和恢复视图状态。 在子片段中,我需要保存并恢复当前选定的项目。 本来我有这样的东西(这是子片段的OnCreateView)

 public override View OnCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstance) { var view = inflater.Inflate(Resource.Layout.CentresList, container, false); // ... other code ommitted ... // DONT DO THIS, SEE BELOW if (savedInstance != null) { // Restore selection _selection = savedInstance.GetString(KEY_SELECTION); } else { // Select first item _selection =_items[0]; } return view; } 

这样做的问题是切换标签时标签主机不会调用OnSaveInstanceState。 相反,子片段保持活动状态,它的_selectionvariables可以保持不变。

所以我将代码移动到OnCreate管理选择:

 public override void OnCreate(Bundle savedInstance) { base.OnCreate(savedInstance); if (savedInstance != null) { // Restore Selection _selection = savedInstance.GetString(BK_SELECTION); } else { // Select first item _selection = _items[0]; } } public override View OnCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstance) { // Don't restore/init _selection here return inflater.Inflate(Resource.Layout.CentresList, container, false); } 

现在,无论是切换标签还是改变方向,这一切似乎都能完美运行。