5

Ich habe eine App mit Hierarchie wie folgt aus:Android FragmentTab Host und Fragmente innerhalb Fragmente

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

Alle Fragment Ansichten von Ressourcen aufgeblasen werden.

Wenn die app beginnt alles erscheint und sieht gut aus. Wenn ich von der ersten Registerkarte zu einer anderen Registerkarte und wieder zurück wechsele, erhalte ich eine Inflate-Ausnahme, um die Ansichten von Registerkarte 1 neu zu erstellen.

ein wenig tiefer Graben, das ist, was passiert:

  • Auf der ersten Last, das Aufblasen der Splitter Ansicht bewirkt, dass sein Kind zwei Fragmente mit dem Fragment-Manager hinzugefügt werden.
  • Beim Umschalten von dem ersten Registerkarte, dann ist es Ansicht zerstört wird, aber es ist Kind-Fragmente in dem Fragmente Manager
  • Beim Umschalten zurück auf den ersten Registerkarte links sind, wird die Ansicht erneut aufgeblasen und da die alten Fragmente Kind sind nach wie vor eine Ausnahme in dem Fragment-Manager wird ausgelöst, wenn die neue Kind Fragmente instanziiert werden (durch Inflation)

ich habe um das funktioniert durch das Kind Fragment aus dem Fragment-Manager zu entfernen (ich verwende Mono) und ich jetzt kann Tabs ohne die Ausnahme wechseln.

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

Also habe ich ein paar Fragen:

  1. Ist der über die richtige Art und Weise, dies zu tun?
  2. Wenn nicht, wie soll ich es tun?
  3. So oder so, wie zB Zustand Krawatte in all diesem nicht sparend, so dass ich nicht Ansichtszustand verlieren, wenn Tabs zu wechseln?

Antwort

2

Ich bin nicht sicher, wie dies in Mono zu tun, aber Kind Fragmente zu einem anderen Fragment hinzufügen möchten, können Sie nicht die FragmentManager des Activity verwenden. Stattdessen müssen Sie die ChildFragmentManager des Hosting Fragment verwenden:

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

Der Haupt FragmentManager der Activity Ihre Tabs behandelt.
Die ChildFragmentManager von tab1 behandelt die geteilte Ansichten.

+0

OK, das klingt vielversprechend. Wie sage ich dem Inflator, den Child Fragment Manager anstelle des übergeordneten zu verwenden? –

+0

Sie fügen das untergeordnete Fragment in das übergeordnete Fragment ein, indem der Code des übergeordneten Fragments den ChildFragmentManager zum Hinzufügen der untergeordneten Fragmente verwendet. Dann stellt der Inflater, den Ihre onCreateView-Callback-Methode Ihres (Kind-) Fragments bereitstellt, zur Verfügung: https://developer.android.com/reference/android/app/Fragment.html#onCreateView (android.view.LayoutInflater, android.view.ViewGroup, android.os.Bundle). –

+0

Sie müssen wahrscheinlich etwas mehr googeln/stapeln, um zu sehen, wie Sie mit Kindfragmenten in Mono umgehen. Ich kann dir nicht helfen (ich benutze Java). –

2

OK, ich das endlich herausgefunden:

Wie oben vorgeschlagen, zunächst änderte es das Fragment Schöpfung programmatisch zu tun, und hatte sie das Kind Fragment-Manager hinzugefügt, etwa so:

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

Wie erwartet, wurde jedes Mal, wenn ich Tabs änderte, eine zusätzliche Instanz von Lhs/RhsFragment erstellt, aber ich bemerkte, dass auch die OnCreateView von Lhs/RhsFragment aufgerufen wurde. Also würde nach jedem Tab-Schalter ein weiterer Aufruf von OnCreateView erfolgen. Wechseln Sie die Tabs 10 mal = 11 Aufrufe von OnCreateView. Das ist offensichtlich falsch.

Mit Blick auf den Quellcode für FragmentTabHost kann ich sehen, dass es einfach trennt und das Inhaltsfragment der Registerkarte beim Wechseln der Registerkarten wieder anfügt. Es scheint, dass ChildFragmentManager des übergeordneten Fragments die untergeordneten Fragmente beibehält und ihre Ansichten automatisch neu erstellt, wenn das übergeordnete Fragment erneut angefügt wird.

So zog ich die Schaffung von Fragmenten zu OnCreate, und nur dann, wenn wir nicht von gespeicherten Zustand Laden:

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

Diese feste die Schaffung der zusätzlichen Ansichten und Schalt Tab ist jetzt im Grunde gearbeitet.

Die nächste Frage war Speichern und Wiederherstellen des Ansichtszustands. In den Kindfragmenten muss ich das aktuell ausgewählte Element speichern und wiederherstellen. Ursprünglich hatte ich so etwas (das ist die OnCreateView Kinder Fragment)

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

Das Problem dabei ist, dass die Registerkarte Host nicht OnSaveInstanceState nicht aufrufen, wenn Registerkarten wechseln. Stattdessen wird das Kindfragment am Leben erhalten und seine _selection-Variable kann einfach in Ruhe gelassen werden.

Also zog ich den Code Auswahl an OnCreate zu verwalten:

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

Jetzt alles perfekt zu funktionieren scheint, sowohl wenn Registerkarten Schalt- und Ausrichtung zu ändern.