Fragmenty wewnątrz fragmentów

145

Zastanawiam się, czy to rzeczywiście błąd w Android API:

Mam taką konfigurację:

┌----┬---------┐
|    |         |
|  1 |    2    |
|    |┌-------┐|
|    ||       ||
|    ||   3   ||
└----┴┴-------┴┘
  1. To menu, które ładuje fragment # 2 (ekran wyszukiwania) w prawym panelu.
  2. Jest ekranem wyszukiwania zawierającym fragment nr 3 będący listą wyników.
  3. Lista wyników jest używana w kilku miejscach (w tym jako funkcjonujący sam w sobie fragment wysokiego poziomu).

Ta funkcja działa doskonale na telefonie (gdzie 1 i 2 i 3 to ActivityFragments).

Jednak gdy użyłem tego kodu:

    FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();       
    Fragment frag = new FragmentNumber2();
    if(toLoad != null) frag.setArguments(toLoad);
    transaction.replace(R.id.rightPane, frag);      
    transaction.commit();

Gdzie R.id.leftPanei R.id.rightPane<fragment>s w poziomym układzie liniowym.

Rozumiem, że powyższy kod usuwa rezydentny fragment, a następnie zastępuje go nowym fragmentem. Świetnie ... Oczywiście tak się nie dzieje, ponieważ gdy ten kod jest uruchamiany po raz drugi, pojawia się następujący wyjątek:

07-27 15:22:55.940: ERROR/AndroidRuntime(8105): Caused by: java.lang.IllegalArgumentException: Binary XML file line #57: Duplicate id 0x7f080024, tag null, or parent id 0x0 with another fragment for FragmentNumber3

Jest to spowodowane tym, że kontener dla FragmentNumber3 został zduplikowany i nie ma już unikalnego identyfikatora. Początkowy fragment nie został zniszczony (?) Przed dodaniem nowego (moim zdaniem oznacza to, że nie został zastąpiony ).

Czy ktoś może mi powiedzieć, czy jest to możliwe ( ta odpowiedź sugeruje, że tak nie jest), czy to błąd?

Graeme
źródło
1
możliwy duplikat Fragment Inside Fragment
rds
6
@rds to starożytne pytanie, które nie ma sensu oznaczać jako duplikatu.
pietv8x

Odpowiedzi:

203

Zagnieżdżone fragmenty nie są obecnie obsługiwane. Próba umieszczenia fragmentu w interfejsie użytkownika innego fragmentu spowoduje niezdefiniowane i prawdopodobnie nieprawidłowe zachowanie.

Aktualizacja : Zagnieżdżone fragmenty są obsługiwane od wersji Androida 4.2 (i biblioteki obsługi systemu Android w wersji 11): http://developer.android.com/about/versions/android-4.2.html#NestedFragments

UWAGA (zgodnie z tym dokumentem ): „ Uwaga: nie można wypełnić układu do fragmentu, gdy układ ten zawiera a <fragment>. Zagnieżdżone fragmenty są obsługiwane tylko wtedy, gdy są dodawane do fragmentu dynamicznie ”.

hackbod
źródło
14
Nieobsługiwane, ponieważ nie było to celem projektowym dla początkowej implementacji. Słyszałem wiele próśb o tę funkcję, więc prawdopodobnie w pewnym momencie zostanie to zrobione, ale jak zwykle jest wiele innych rzeczy, które konkurują z nią w pierwszej kolejności.
hackbod
4
Udało mi się to, rozszerzając FragmentActivity, FragmentManager i FragmentTransaction. Podstawowym założeniem jest rozszerzenie DeferringFragmentActivity w moich działaniach, zapewniając ten sam interfejs API, więc nie ma innych zmian w kodzie. Kiedy wywołuję getFragmentManager, otrzymuję wystąpienie DeferringFragmentManager, a kiedy wywołuję beginTransaction, otrzymuję DeferredTransaction. Ta transakcja przechowuje POJO z wywołaną metodą i argumentami. Gdy wywołanie commit jest wywoływane, najpierw szukamy wszelkich oczekujących transakcji DeferredTransactions. Po zatwierdzeniu wszystkich transakcji rozpoczynamy prawdziwą transakcję i uruchamiamy wszystkie przechowywane metody z argumentami.
dskinner
11
To jest teraz. Zagnieżdżone pliki Fragmentsą teraz częścią interfejsu API systemu Android, ojej! developer.android.com/about/versions/… .
Alex Lockwood
9
Wow, co za koszmar: jeśli użyjesz <fragment> na fragmencie, a ten fragment zdarzy się użyć fragmentów podrzędnych, nie kończy się to z wyraźnym błędem („nie można dodać fragmentów podrzędnych do fragmentów układu”) - to zawodzi tajemniczo z wyjątkami typu „fragment nie stworzył widoku”. Trwa kilka godzin debugowania ...
Glenn Maynard,
6
@ MartínMarconcini, oczywiście, ale nie jest to wcale oczywiste, biorąc pod uwagę funkcjonalność zapewnianą przez API. Jeśli coś jest niedozwolone, powinno to być wyraźnie udokumentowane, a nie pozostawione programiście, aby wyrywał włosy, ponieważ coś nie działa tak, jak byś tego oczekiwał.
dcow
98

Zagnieżdżone fragmenty są obsługiwane w systemie Android 4.2 i nowszych

Biblioteka obsługi systemu Android obsługuje teraz również zagnieżdżone fragmenty , dzięki czemu można wdrażać projekty zagnieżdżonych fragmentów w systemie Android 1.6 i nowszych.

Aby zagnieździć fragment, po prostu wywołaj funkcję getChildFragmentManager () na fragmencie, do którego chcesz dodać fragment. Zwraca to FragmentManager, którego można używać tak, jak zwykle w działaniu najwyższego poziomu do tworzenia transakcji fragmentarycznych. Na przykład, oto kod, który dodaje fragment z istniejącej klasy Fragment:

Fragment videoFragment = new VideoPlayerFragment();
FragmentTransaction transaction = getChildFragmentManager().beginTransaction();
transaction.add(R.id.video_fragment, videoFragment).commit();

Aby dowiedzieć się więcej o zagnieżdżonych fragmentach, zapoznaj się z samouczkami
Część 1
Część 2
Część 3

a tutaj jest post SO, który omawia najlepsze praktyki dla zagnieżdżonych fragmentów .

Raneez Ahmed
źródło
główną wadą Nestedfragment jest to, że nie możemy wywołać menu opcji z childfragment :( jeśli używamy ABS!
LOG_TAG
Czy możesz zajrzeć do mojego numeru? Jest bardzo podobny ... stackoverflow.com/questions/32240138/… . Dla mnie framnet dziecka nie jest napompowany przez kod
Nicks
33

.. możesz wyczyścić zagnieżdżony fragment w destroyviewmetodzie fragmentu nadrzędnego :

@Override
    public void onDestroyView() {

      try{
        FragmentTransaction transaction = getSupportFragmentManager()
                .beginTransaction();

        transaction.remove(nestedFragment);

        transaction.commit();
      }catch(Exception e){
      }

        super.onDestroyView();
    }
furykid
źródło
4
Jeśli wykonasz testy cyklu życia za pomocą SetAlwaysFinish ( bricolsoftconsulting.com/2011/12/23/… ), zobaczysz, że ten kod powoduje błąd, gdy inna aktywność jest na wierzchu z włączonym zawsze zakończeniem (IllegalStateException: Can not perform this action po onSaveInstanceState). Zawinięcie powyższego kodu w try / catch nie jest najbardziej eleganckim rozwiązaniem, ale wydaje się, że wszystko działa.
Theo,
To prawie zadziałało. Później dostałem Stackoverflow na temat rysowania interfejsu użytkownika. Zdecydowanie unikaj zagnieżdżonych fragmentów ...
neteinstein
14

Mam aplikację, którą tworzę, która ma podobny układ z zakładkami na pasku akcji, która uruchamia fragmenty, niektóre z tych fragmentów mają w sobie wiele osadzonych fragmentów.

Otrzymałem ten sam błąd, gdy próbowałem uruchomić aplikację. Wygląda na to, że jeśli utworzysz wystąpienie fragmentów w układzie XML po odznaczeniu karty, a następnie ponownym wybraniu, wystąpi błąd inflatora.

Rozwiązałem to, zastępując wszystkie fragmenty w xml Linearlayouts, a następnie używając menedżera fragmentów / transakcji fragmentów do utworzenia instancji fragmentów, wszystko wydaje się działać poprawnie, przynajmniej na poziomie testowym.

Mam nadzieję, że to ci pomoże.

draksia
źródło
Czy ktoś może wypowiedzieć się na temat skuteczności tego podejścia? Uważam, że to niefortunne, że mogę używać Fragmentów tylko na jeden poziom głębokości - równie dobrze mogę ich wtedy w ogóle nie używać. Dodanie ich programowo do zastępczych grup widoków będzie działać bez zastrzeżeń?
Rafael Nobre
Wydaje się, że nadal działają dla mnie, bez problemu zamieniam je w widza i poza nim. Jedno zastrzeżenie, robię to tylko na plastrze miodu, który nie jest zgodny z kanapką z lodami.
draksia
4

Zmierzyłem się z tym samym problemem, zmagałem się z nim przez kilka dni i powinienem powiedzieć, że najłatwiejszym sposobem rozwiązania tego problemu jest użycie fragment.hide () / fragment.show (), gdy karta jest zaznaczona / niezaznaczona ().

public void onTabUnselected(ActionBar.Tab tab, FragmentTransaction ft)
{
    if (mFragment != null)
        ft.hide(mFragment);
}

Gdy nastąpi obrót ekranu, wszystkie fragmenty nadrzędne i podrzędne zostaną poprawnie zniszczone.

Takie podejście ma również jedną dodatkową zaletę - użycie funkcji hide () / show () nie powoduje utraty stanu widoków fragmentów, więc nie ma potrzeby przywracania poprzedniej pozycji przewijania na przykład dla ScrollViews.

Problem w tym, że nie wiem, czy dobrze jest nie odrywać fragmentów, gdy nie są one widoczne. Myślę, że oficjalny przykład TabListener został zaprojektowany z myślą o tym, że fragmenty są wielokrotnego użytku i nie powinieneś zanieczyszczać ich pamięcią, jednak myślę, że jeśli masz tylko kilka zakładek i wiesz, że użytkownicy będą często się między nimi przełączać będzie właściwe, aby zachować ich przywiązanie do bieżącej działalności.

Chciałbym usłyszeć komentarze od bardziej doświadczonych programistów.

ievgen
źródło
0

Jeśli okaże się, że zagnieżdżony fragment nie jest usuwany lub kopiowany (np. Po ponownym uruchomieniu działania, przy obrocie ekranu), spróbuj zmienić:

transaction.add(R.id.placeholder, newFragment);

do

transaction.replace(R.id.placeholder, newFragment);

Jeśli powyższe nie pomoże, spróbuj:

Fragment f = getChildFragmentManager().findFragmentById(R.id.placeholder);

FragmentTransaction transaction = getChildFragmentManager().beginTransaction();

if (f == null) {
    Log.d(TAG, "onCreateView: fragment doesn't exist");
    newFragment= new MyFragmentType();
    transaction.add(R.id.placeholder, newFragment);
} else {
    Log.d(TAG, "onCreateView: fragment already exists");
    transaction.replace(R.id.placeholder, f);
}
transaction.commit();

Nauczyłem się tutaj

Voy
źródło