Узел Android FragmentTab и фрагменты внутри фрагментов

У меня есть приложение с такой иерархией:

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(); } 

Поэтому у меня есть несколько вопросов:

  1. Правильно ли это сделано?
  2. Если нет, как я должен это делать?
  3. В любом случае, как сохранение состояния экземпляра связывает все это, чтобы я не терял состояние отображения при переключении вкладок?

Я не уверен, как это сделать в Mono, но чтобы добавить дочерние фрагменты к другому фрагменту, вы не можете использовать FragmentManager Activity . Вместо этого вы должны использовать ChildFragmentManager хостинга Fragment :

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

Основной FragmentManager Activity обрабатывает ваши вкладки.
ChildFragmentManager tab1 обрабатывает разделенные виды.

Хорошо, я, наконец, понял это:

Как было предложено выше, сначала я изменил создание фрагмента, которое будет выполняться программно, и добавили ли они в диспетчер дочерних фрагментов, например:

 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, но я заметил, что также будет вызван старый OnCreateView Lhs / RhsFragment. Таким образом, после каждого переключения табуляции в OnCreateView будет еще один вызов. Переключение вкладок 10 раз = 11 вызовов OnCreateView. Это, очевидно, неправильно.

Посмотрев исходный код для FragmentTabHost, я вижу, что он просто отделяет и повторно прикрепляет фрагмент содержимого вкладки при переключении вкладок. Кажется, родительский Fragment's 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); } 

Теперь все работает отлично, как при переключении вкладок, так и при изменении ориентации.