Problemy z tylnym stosem fragmentów Androida


121

Mam ogromny problem ze sposobem, w jaki wydaje się działać backstack fragmentów Androida i byłbym bardzo wdzięczny za każdą oferowaną pomoc.

Wyobraź sobie, że masz 3 fragmenty

[1] [2] [3]

Chcę, aby użytkownik mógł nawigować, [1] > [2] > [3]ale w drodze powrotnej (naciskając przycisk Wstecz) [3] > [1].

Jak sobie wyobrażałem, byłoby to osiągnięte bez wywoływania addToBackStack(..)podczas tworzenia transakcji, która wprowadza fragment [2]do posiadacza fragmentu zdefiniowanego w XML.

W rzeczywistości wydaje mi się, że jeśli nie chcę [2]się pojawiać ponownie, gdy użytkownik naciśnie przycisk powrotu [3], nie mogę wywoływać addToBackStacktransakcji, która pokazuje fragment [3]. Wydaje się to całkowicie sprzeczne z intuicją (być może pochodzi ze świata iOS).

W każdym razie, jeśli zrobię to w ten sposób, kiedy wyjdę z [1] > [2]i wrócę, wrócę [1]zgodnie z oczekiwaniami.

Jeśli pójdę [1] > [2] > [3]i cofnę się, wskoczę z powrotem do [1](zgodnie z oczekiwaniami). Teraz dziwne zachowanie ma miejsce, gdy próbuję [2]ponownie przeskoczyć do [1]. Przede wszystkim [3]jest krótko wyświetlany, zanim [2]pojawi się w widoku. Jeśli naciśnę wstecz w tym momencie, [3]zostanie wyświetlony komunikat, a jeśli naciśnę ponownie, aplikacja zostanie zamknięta.

Czy ktoś może mi pomóc zrozumieć, co się tutaj dzieje?


A oto plik xml układu dla mojej głównej działalności:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
          android:layout_width="fill_parent"
          android:layout_height="fill_parent"
          android:orientation="vertical" >

<fragment
        android:id="@+id/headerFragment"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        class="com.fragment_test.FragmentControls" >
    <!-- Preview: layout=@layout/details -->
</fragment>
<FrameLayout
        android:id="@+id/detailFragment"
        android:layout_width="match_parent"
        android:layout_height="fill_parent"

        />



Aktualizacja To jest kod, którego używam do tworzenia według heirarchii nawigacyjnej

    Fragment frag;
    FragmentTransaction transaction;


    //Create The first fragment [1], add it to the view, BUT Dont add the transaction to the backstack
    frag = new Fragment1();

    transaction = getSupportFragmentManager().beginTransaction();
    transaction.replace(R.id.detailFragment, frag);
    transaction.commit();

    //Create the second [2] fragment, add it to the view and add the transaction that replaces the first fragment to the backstack
    frag = new Fragment2();

    transaction = getSupportFragmentManager().beginTransaction();
    transaction.replace(R.id.detailFragment, frag);
    transaction.addToBackStack(null);
    transaction.commit();


    //Create third fragment, Dont add this transaction to the backstack, because we dont want to go back to [2] 
    frag = new Fragment3();
    transaction = getSupportFragmentManager().beginTransaction();
    transaction.replace(R.id.detailFragment, frag);
    transaction.commit();


     //END OF SETUP CODE-------------------------
    //NOW:
    //Press back once and then issue the following code:
    frag = new Fragment2();
    transaction = getSupportFragmentManager().beginTransaction();
    transaction.replace(R.id.detailFragment, frag);
    transaction.addToBackStack(null);
    transaction.commit();

    //Now press back again and you end up at fragment [3] not [1]

Wielkie dzięki


ale fragment nakłada się, kiedy cofam się z fragmentu C do fragmentu A.
Priyanka

mam ten sam problem, jak to naprawić?
Priyanka

skieruj moje odpowiedzi ... może ci pomóc < stackoverflow.com/questions/14971780/… >
MD Khali

Odpowiedzi:


203

Wyjaśnienie: o co tu chodzi?

Jeśli pamiętamy, że .replace()jest to równe .remove().add()temu, które znamy z dokumentacji:

Zastąp istniejący fragment, który został dodany do kontenera. Jest to zasadniczo to samo, co wywołanie remove(Fragment)wszystkich aktualnie dodanych fragmentów, które zostały dodane z tym samym, containerViewIda następnie add(int, Fragment, String)z tymi samymi argumentami podanymi tutaj.

to, co się dzieje, wygląda tak (dodaję liczby do fragmentu, aby było bardziej zrozumiałe):

// transaction.replace(R.id.detailFragment, frag1);
Transaction.remove(null).add(frag1)  // frag1 on view

// transaction.replace(R.id.detailFragment, frag2).addToBackStack(null);
Transaction.remove(frag1).add(frag2).addToBackStack(null)  // frag2 on view

// transaction.replace(R.id.detailFragment, frag3);
Transaction.remove(frag2).add(frag3)  // frag3 on view

(tutaj zaczynają się dziać wszystkie wprowadzające w błąd rzeczy)

Pamiętaj, że .addToBackStack()zapisuje tylko transakcję, a nie fragment jako siebie! Więc teraz mamy frag3na układzie:

< press back button >
// System pops the back stack and find the following saved back entry to be reversed:
// [Transaction.remove(frag1).add(frag2)]
// so the system makes that transaction backward!!!
// tries to remove frag2 (is not there, so it ignores) and re-add(frag1)
// make notice that system doesn't realise that there's a frag3 and does nothing with it
// so it still there attached to view
Transaction.remove(null).add(frag1) //frag1, frag3 on view (OVERLAPPING)

// transaction.replace(R.id.detailFragment, frag2).addToBackStack(null);
Transaction.remove(frag3).add(frag2).addToBackStack(null)  //frag2 on view

< press back button >
// system makes saved transaction backward
Transaction.remove(frag2).add(frag3) //frag3 on view

< press back button >
// no more entries in BackStack
< app exits >

Możliwe rozwiązanie

Rozważ wdrożenie, FragmentManager.BackStackChangedListeneraby obserwować zmiany w stosie wstecznym i zastosować swoją logikę w onBackStackChanged()metodzie:


Dobre wyjaśnienie @arvis. Jak jednak możemy zapobiec takiemu zachowaniu bez uciekania się do hackerskich sposobów, takich jak ten z DexterMoon lub ten z Nemanja za pomocą popBackStack, który pokazuje fragment podczas odtwarzania animacji przejścia?
momo

@momo możesz zaimplementować, FragmentManager.BackStackChangedListeneraby obserwować zmiany w stosie tylnym. Monitoruj wszystkie swoje transakcje onBackStackChanged()metodą i działaj w razie potrzeby: np. śledzić liczbę transakcji w BackStack; sprawdź konkretną transakcję po nazwie ( FragmentTransaction addToBackStack (String name)) itd.
Arvis

Dzięki za odpowiedź. Właściwie spróbowałem tego wcześniej, zarejestrowałem słuchacza i usunąłem fragment z onBackstackChange. Podczas odtwarzania trzaskającego przejścia fragment staje się białym pustym obszarem. Chyba metoda jest odpalana, gdy popping uruchamia animację, a nie kiedy się kończy ...
momo

4
Unikaj używania tylnych stosów! to naprawdę nie pomaga w ogólnej wydajności! użyj zwykłego zamień () lub jeszcze lepiej usuń / dodaj za każdym razem, gdy chcesz nawigować!
stack_ved,

@ Arvis czy u pls mogą mi pomóc, otrzymuję ten sam problem ... liczba stosów 0, ale nadal mój fragment jest widoczny?
Erum

33

Dobrze!!! po wielu wyrywaniu włosów w końcu wymyśliłem, jak to działa.

Wygląda na to, że fragment [3] nie jest usuwany z widoku po naciśnięciu przycisku wstecz, więc musisz to zrobić ręcznie!

Przede wszystkim nie używaj replace (), ale zamiast tego użyj usuń i dodaj oddzielnie. Wygląda na to, że funkcja replace () nie działa poprawnie.

Następną częścią jest zastąpienie metody onKeyDown i usunięcie bieżącego fragmentu za każdym razem, gdy naciśnięty zostanie przycisk Wstecz.

@Override
public boolean onKeyDown(int keyCode, KeyEvent event)
{
    if (keyCode == KeyEvent.KEYCODE_BACK)
    {
        if (getSupportFragmentManager().getBackStackEntryCount() == 0)
        {
            this.finish();
            return false;
        }
        else
        {
            getSupportFragmentManager().popBackStack();
            removeCurrentFragment();

            return false;
        }



    }

    return super.onKeyDown(keyCode, event);
}


public void removeCurrentFragment()
{
    FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();

    Fragment currentFrag =  getSupportFragmentManager().findFragmentById(R.id.detailFragment);


    String fragName = "NONE";

    if (currentFrag!=null)
        fragName = currentFrag.getClass().getSimpleName();


    if (currentFrag != null)
        transaction.remove(currentFrag);

    transaction.commit();

}

Mam nadzieję że to pomoże!


to działa, ale nie można tego użyć w moim projekcie, ponieważ fragment zostanie ponownie załadowany! jakieś sugestie?
TharakaNirmana,

Ale jeśli to robię. Kiedy wracam z punktu C do punktu A, po fragmencie C pojawia się pusty ekran
Nigam Patro

Podejrzewam, że odpowiedź @Arvis powinna rzucić trochę światła na twój problem
Chris Birch

16

Przede wszystkim dziękuję @Arvis za otwierające oczy wyjaśnienie.

Wolę inne rozwiązanie niż zaakceptowana tutaj odpowiedź na ten problem. Nie lubię majstrować przy zastępowaniu zachowania wstecz, bardziej niż jest to absolutnie konieczne, a kiedy próbowałem samodzielnie dodawać i usuwać fragmenty bez domyślnego wyskakującego stosu po naciśnięciu przycisku Wstecz, znalazłem się w piekle fragmentów :) Jeśli ty. dodaj f2 do f1, gdy go usuniesz, f1 nie wywoła żadnej z metod wywołania zwrotnego, takich jak onResume, onStart itp. i może to być bardzo niefortunne.

W każdym razie tak to robię:

Obecnie na wyświetlaczu jest tylko fragment f1.

f1 -> f2

Fragment2 f2 = new Fragment2();
this.getActivity().getSupportFragmentManager().beginTransaction().replace(R.id.main_content,f2).addToBackStack(null).commit();

nic niezwykłego tutaj. Następnie we fragmencie f2 ten kod prowadzi do fragmentu f3.

f2 -> f3

Fragment3 f3 = new Fragment3();
getActivity().getSupportFragmentManager().popBackStack();
getActivity().getSupportFragmentManager().beginTransaction().replace(R.id.main_content, f3).addToBackStack(null).commit();

Nie jestem pewien, czy czytając dokumentację, czy to powinno zadziałać, mówi się, że ta metoda transakcji poping jest asynchroniczna i być może lepszym sposobem byłoby wywołanie popBackStackImmediate (). Ale o ile mogę stwierdzić, na moich urządzeniach działa bez zarzutu.

Wspomniana alternatywa to:

final FragmentActivity activity = getActivity();
activity.getSupportFragmentManager().popBackStackImmediate();
activity.getSupportFragmentManager().beginTransaction().replace(R.id.main_content, f3).addToBackStack(null).commit();

Tutaj faktycznie będzie krótki powrót do f1 przed przejściem do f3, więc tam drobny błąd.

To właściwie wszystko, co musisz zrobić, nie ma potrzeby zastępowania zachowania stosu wstecznego ...


6
ale usterka, to jest problem
Zyoo

Wydaje się to mniej "hack-ey" niż nasłuchiwanie zmian w BackStack. Dzięki.
ahaisting

13

Wiem, że to stara kwestia, ale mam ten sam problem i naprawię go w ten sposób:

Najpierw dodaj Fragment1 do BackStack z nazwą (np. „Frag1”):

frag = new Fragment1();

transaction = getSupportFragmentManager().beginTransaction();
transaction.replace(R.id.detailFragment, frag);
transaction.addToBackStack("Frag1");
transaction.commit();

A potem, ilekroć chcesz wrócić do Fragmentu1 (nawet po dodaniu 10 fragmentów nad nim), po prostu wywołaj popBackStackImmediate z nazwą:

getSupportFragmentManager().popBackStackImmediate("Frag1", 0);

Mam nadzieję, że to komuś pomoże :)


1
Świetna odpowiedź. Musiałem użyć getSupportFragmentManager (). PopBackStackImmediate ("Frag1", FragmentManager.POP_BACK_STACK_INCLUSIVE); aby to działało w moim przypadku użycia
eliasbagley

1
Gdzie mam umieścić ten kod ???? getSupportFragmentManager (). popBackStackImmediate ("Frag1", 0); On MainActivty lub onBackPress
pavel

to nie działa. :( to jest mój kod, w moim przypadku Fragment się nakłada. otwiera fragment A, ale fragment A nakłada się na fragment B.
Priyanka

FragmentManager fragmentManager = getActivity (). GetSupportFragmentManager (); fragmentManager.popBackStack (FragmentA.class.getName (), FragmentManager.POP_BACK_STACK_INCLUSIVE); FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction (); FragmentE fragmentE = new FragmentE (); fragmentTransaction.replace (R.id.fragment_content, fragmentE, fragmentE.getClass (). getName ()); fragmentTransaction.commit ();
Priyanka

5

Po odpowiedzi @Arvis postanowiłem poszukać jeszcze głębiej i napisałem artykuł techniczny na ten temat tutaj: http://www.andreabaccega.com/blog/2015/08/16/how-to-avoid-fragments-overlapping- z powodu koszmaru-backstack-in-android /

Dla leniwych programistów dookoła. Moje rozwiązanie polega na zawsze dodawaniu transakcji do backstacka i wykonywaniu dodatkowychFragmentManager.popBackStackImmediate() gdy jest to potrzebne (automatycznie).

Kod składa się z bardzo niewielu wierszy kodu, aw moim przykładzie chciałem przeskoczyć z C do A bez przeskakiwania z powrotem do „B”, jeśli użytkownik nie wszedł głębiej w backstack (np. Z C przechodzi do D).

Stąd załączony kod działałby następująco A -> B -> C (tył) -> A & A -> B -> C -> D (tył) -> C (tył) -> B (tył) -> A

gdzie

fm.beginTransaction().replace(R.id.content, new CFragment()).commit()

zostały wydane od „B” do „C” jak w pytaniu.

Ok, ok tu jest kod :)

public static void performNoBackStackTransaction(FragmentManager fragmentManager, String tag, Fragment fragment) {
  final int newBackStackLength = fragmentManager.getBackStackEntryCount() +1;

  fragmentManager.beginTransaction()
      .replace(R.id.content, fragment, tag)
      .addToBackStack(tag)
      .commit();

  fragmentManager.addOnBackStackChangedListener(new FragmentManager.OnBackStackChangedListener() {
    @Override
    public void onBackStackChanged() {
      int nowCount = fragmentManager.getBackStackEntryCount();
      if (newBackStackLength != nowCount) {
        // we don't really care if going back or forward. we already performed the logic here.
        fragmentManager.removeOnBackStackChangedListener(this);

        if ( newBackStackLength > nowCount ) { // user pressed back
          fragmentManager.popBackStackImmediate();
        }
      }
    }
  });
}

1
uzyskiwanie awarii w tej linii fragmentManager.popBackStackImmediate (); błąd: java.lang.IllegalStateException: FragmentManager wykonuje już transakcje pod adresem com.example.myapplication.FragmentA 2 $ onBackStackChanged (FragmentA.java:43)
Priyanka

1

Jeśli zmagasz się z addToBackStack () i popBackStack (), po prostu użyj

FragmentTransaction ft =getSupportFragmentManager().beginTransaction();
ft.replace(R.id.content_frame, new HomeFragment(), "Home");
ft.commit();`

W swojej aktywności w OnBackPressed () znajdź fargowanie według tagu, a następnie rób swoje

Fragment home = getSupportFragmentManager().findFragmentByTag("Home");

if (home instanceof HomeFragment && home.isVisible()) {
    // do you stuff
}

Aby uzyskać więcej informacji https://github.com/DattaHujare/NavigationDrawer Nigdy nie używam addToBackStack () do obsługi fragmentu.


0

Myślę, że kiedy czytam twoją historię, [3] jest również na zapleczu. To wyjaśnia, dlaczego widzisz, jak miga.

Rozwiązaniem byłoby nigdy nie umieszczać [3] na stosie.


Cześć jdekei Dzięki za wkład. Problem polega na tym, że nie widzę, gdzie dodam [3] do backstacka. Dodałem kolejny fragment kodu, który demonstruje (programowo) dokładnie nawigację, którą wykonywałem za pomocą przycisków.
Chris Birch,

Mnie też to pomaga, ale używam tylko removeCurFragment z twojego kodu i trochę innego sprawdzania fragmentów. Metoda zamiany działa dla mnie dobrze, może być w s old method issue but now itporządku. Dzięki
Viktor V.

0

Miałem podobny problem, gdzie miałem 3 kolejne fragmenty w tym samym Activity[M1.F0] -> [M1.F1] -> [M1.F2], po których następowało wezwanie do nowegoActivity [M2]. Jeśli użytkownik nacisnął przycisk w [M2], chciałem wrócić do [M1, F1] zamiast do [M1, F2], co już robiło zachowanie wstecznego naciśnięcia.

W tym celu usuwam [M1, F2], wywołuję show na [M1, F1], zatwierdzam transakcję, a następnie dodaję z powrotem [M1, F2], wywołując ją z użyciem funkcji hide. Spowodowało to usunięcie dodatkowej prasy wstecznej, która w przeciwnym razie zostałaby pozostawiona.

// Remove [M1.F2] to avoid having an extra entry on back press when returning from M2
final FragmentTransaction ftA = fm.beginTransaction();
ftA.remove(M1F2Fragment);
ftA.show(M1F1Fragment);
ftA.commit();
final FragmentTransaction ftB = fm.beginTransaction();
ftB.hide(M1F2Fragment);
ftB.commit();

Cześć Po wykonaniu tego kodu: Nie mogę zobaczyć wartości Fragment2 po naciśnięciu klawisza Wstecz. Mój kod:

FragmentTransaction ft = fm.beginTransaction();
ft.add(R.id.frame, f1);
ft.remove(f1);

ft.add(R.id.frame, f2);
ft.addToBackStack(null);

ft.remove(f2);
ft.add(R.id.frame, f3);

ft.commit();

@Override
    public boolean onKeyDown(int keyCode, KeyEvent event){

        if(keyCode == KeyEvent.KEYCODE_BACK){
            Fragment currentFrag =  getFragmentManager().findFragmentById(R.id.frame);
            FragmentTransaction transaction = getFragmentManager().beginTransaction();

            if(currentFrag != null){
                String name = currentFrag.getClass().getName();
            }
            if(getFragmentManager().getBackStackEntryCount() == 0){
            }
            else{
                getFragmentManager().popBackStack();
                removeCurrentFragment();
            }
       }
    return super.onKeyDown(keyCode, event);
   }

public void removeCurrentFragment()
    {
        FragmentTransaction transaction = getFragmentManager().beginTransaction();
        Fragment currentFrag =  getFragmentManager().findFragmentById(R.id.frame);

        if(currentFrag != null){
            transaction.remove(currentFrag);
        }
        transaction.commit();
    }

0

executePendingTransactions() , commitNow() nie pracował (

Pracował w systemie Androidx (jetpack).

private final FragmentManager fragmentManager = getSupportFragmentManager();

public void removeFragment(FragmentTag tag) {
    Fragment fragmentRemove = fragmentManager.findFragmentByTag(tag.toString());
    if (fragmentRemove != null) {
        fragmentManager.beginTransaction()
                .remove(fragmentRemove)
                .commit();

        // fix by @Ogbe
        fragmentManager.popBackStackImmediate(tag.toString(), 
            FragmentManager.POP_BACK_STACK_INCLUSIVE);
    }
}
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.