Android FragmentTab主机和片段中的片段

Android FragmentTab主机和片段中的片段,android,android-fragments,xamarin.android,android-tabhost,android-nested-fragment,Android,Android Fragments,Xamarin.android,Android Tabhost,Android Nested Fragment,我有一个层次结构如下的应用程序: FragmentTabHost (Main Activity) - Fragment (tab 1 content - splitter view) - Fragment (lhs, list) - Framment (rhs, content view) - Fragment (tab 2 content) - Fragment (tab 2 content) 所有片段视图都从资源中膨胀 当应用程序启动时,所有内容都会显示出来并看

我有一个层次结构如下的应用程序:

FragmentTabHost (Main Activity)
  - Fragment (tab 1 content - splitter view)
    - Fragment (lhs, list)
    - Framment (rhs, content view)
  - Fragment (tab 2 content)
  - Fragment (tab 2 content)
所有片段视图都从资源中膨胀

当应用程序启动时,所有内容都会显示出来并看起来很好。当我从第一个选项卡切换到另一个选项卡,然后再切换回来时,尝试重新创建选项卡1的视图时会出现充气异常

再深入一点,事情就是这样:

  • 在第一次加载时,膨胀拆分器视图会将其两个子片段添加到片段管理器中
  • 从第一个选项卡切换时,它的视图被销毁,但它的子片段保留在片段管理器中
  • 切换回第一个选项卡时,视图将重新膨胀,并且由于旧的子片段仍在片段管理器中,因此在实例化新的子片段时(通过膨胀)会引发异常
我通过从片段管理器中删除子片段(我使用的是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();
}
所以我有几个问题:

  • 上述方法正确吗
  • 如果没有,我应该怎么做
  • 不管怎样,保存实例状态如何与所有这些联系在一起,以便在切换选项卡时不会丢失视图状态

  • 我不知道如何在Mono中实现这一点,但要将子片段添加到另一个片段,您不能使用
    活动的
    片段管理器
    。相反,您必须使用托管
    片段的
    ChildFragmentManager

    活动的主
    FragmentManager
    处理您的选项卡。

    tab1
    ChildFragmentManager
    处理分割视图

    好吧,我终于明白了:

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

    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的源代码,我可以看到它只是在切换选项卡时分离并重新附加选项卡的内容片段。父片段的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)

    问题是切换选项卡时,选项卡主机不调用OnSaveInstanceState。相反,子片段是保持活动的,它的_选择变量可以不受影响

    因此,我将管理选择的代码移动到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);
    }
    

    现在,无论是在切换标签还是改变方向时,这一切似乎都工作得很好。

    好的,听起来很有希望。如何告诉充气机使用子片段管理器而不是父片段管理器?通过让父片段的代码使用子片段管理器添加其子片段,可以在父片段中添加子片段。然后,您的(子)片段的onCreateView回调方法提供的充气器:,android.view.ViewGroup,android.os.Bundle)。您可能需要进行更多的Google/StackOverlow操作,以了解如何在Mono中处理子片段。在这方面我帮不了你(我使用Java)。Mono几乎完全一样。。。这是相同的api,不同的语言。我不明白的是:“通过让父片段的代码使用ChildFragmentManager来添加它的子片段”。子片段是在父片段视图的axml中定义的,因此在父视图膨胀时,它们被实例化并添加到片段管理器中。那么,我如何告诉充气机使用子碎片管理器呢?是否应该忘记充气机并手动创建子碎片?
    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);
    }