Android FragmentTabHost和嵌套Fragments

6

我有一个类似于这样的应用程序层次结构:

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

所有的片段视图都是从资源文件中膨胀生成的。

当应用程序启动时,一切都看起来很好。但是,当我从第一个选项卡切换到另一个选项卡,然后再次返回到第一个选项卡时,我会遇到充气异常,尝试重新创建第一个选项卡的视图。

进一步挖掘,这就是发生的情况:

  • 在第一次加载时,充气分裂器视图会导致其两个子片段添加到片段管理器中。
  • 在切换离开第一个选项卡时,它的视图被销毁,但其子片段留在片段管理器中
  • 当切换回第一个选项卡时,该视图被重新充气,由于旧的子片段仍在片段管理器中,当充气实例化新的子片段时,就会抛出异常。

我通过从片段管理器中删除子片段(我正在使用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. 无论哪种方式,如何保存实例状态以便在切换选项卡时不会丢失视图状态?
2个回答

3

好的,听起来很有前途。我该如何告诉充气机使用子片段管理器而不是父级管理器? - Brad Robinson
通过使用父片段的代码使用ChildFragmentManager添加其子项,您可以将子片段添加到父片段中。然后,在(子)片段的onCreateView回调方法提供的inflater中:https://developer.android.com/reference/android/app/Fragment.html#onCreateView(android.view.LayoutInflater,android.view.ViewGroup,android.os.Bundle)。 - Streets Of Boston
你可能需要再搜索/查找一些关于如何在Mono中处理子片段的信息。我无法帮助你(因为我使用Java)。 - Streets Of Boston
Mono基本上完全相同...它是相同的API,不同的语言。我不明白的是这一点:"通过让父片段的代码使用ChildFragmentManager来添加其子项"。子片段在父片段视图的axml中定义,因此它们在实例化并添加到片段管理器时,作为父视图被填充。那么,我该如何告诉充气机使用子片段管理器?应该忘记充气机并手动创建子片段吗? - Brad Robinson

2

好的,我终于搞清楚了:

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

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。相反,子片段被保持活动状态,它的“_selection”变量可以被保留不变。
因此,我将管理选择的代码移至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);
}

现在一切似乎都完美地运作,无论是在切换选项卡还是改变方向时。

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