wake-up-neo.com

Probleme mit dem Android Fragment Back Stack

Ich habe ein riesiges Problem mit der Art und Weise, wie der Android-Fragment-Backstack zu funktionieren scheint und wäre für jede Hilfe, die angeboten wird, sehr dankbar.

Stellen Sie sich vor, Sie haben 3 Fragmente

[1] [2] [3]

Ich möchte, dass der Benutzer in der Lage ist, [1] > [2] > [3] zu navigieren, aber auf dem Rückweg (durch Drücken der Zurück-Taste) [3] > [1].

Wie ich es mir vorgestellt hätte, würde dies erreicht werden, indem addToBackStack(..) nicht aufgerufen wird, wenn eine Transaktion erstellt wird, die Fragment [2] in den in XML definierten Fragment-Halter bringt.

In der Realität scheint es so, als ob, wenn ich nicht möchte, dass [2] erneut erscheint, wenn der Benutzer die Schaltfläche auf [3] drückt, muss ich addToBackStack nicht in der Transaktion aufrufen, die Fragment [3] zeigt. Dies scheint völlig kontraintuitiv zu sein (vielleicht aus der iOS-Welt).

Wie auch immer, wenn ich es so mache, wenn ich von [1] > [2] gehe und zurück drücke, komme ich wie erwartet wieder bei [1] an.

Wenn ich [1] > [2] > [3] gehe und dann zurück drücke, springe ich zurück zu [1] (wie erwartet) . Nun passiert das seltsame Verhalten, wenn ich versuche, [2] erneut von [1] zu springen. Zunächst wird [3] kurz angezeigt, bevor [2] angezeigt wird. Wenn ich an dieser Stelle zurückschalte, wird [3] angezeigt, und wenn ich noch einmal zurückschalte, wird die App beendet.

Kann mir jemand helfen zu verstehen, was hier los ist? 


Und hier ist die Layout-XML-Datei für meine Hauptaktivität:

<?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: [email protected]/details -->
</fragment>
<FrameLayout
        Android:id="@+id/detailFragment"
        Android:layout_width="match_parent"
        Android:layout_height="fill_parent"

        />



Update Dies ist der Code, den ich zum Erstellen von Nav-Hierarchie verwende

    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]

Danke vielmals

112
Chris Birch

Erklärung: was ist hier los?

Wenn wir bedenken, dass .replace() gleich .remove().add() ist, wissen wir aus der Dokumentation:

Ersetzen Sie ein vorhandenes Fragment, das einem Container hinzugefügt wurde. Dies ist im Wesentlichen das Gleiche wie der Aufruf von remove(Fragment) für alle aktuell hinzugefügten Fragmente, die mit derselben containerViewId und dann add(int, Fragment, String) mit den gleichen Argumenten hier hinzugefügt wurden.

dann ist das, was passiert, so (ich füge dem frag Zahlen hinzu, um es klarer zu machen):

// 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

(hier beginnt alles irreführende Zeug zu passieren)

Denken Sie daran, dass .addToBackStack() nur transaction speichert, nicht das fragment als sich selbst! Jetzt haben wir frag3 im Layout:

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

Mögliche Lösung

Erwägen Sie die Implementierung von FragmentManager.BackStackChangedListener , um nach Änderungen im Backstack zu suchen, und wenden Sie Ihre Logik in onBackStackChanged() methode an:

193
Arvis

Recht!!! Nach langem Haarziehen habe ich endlich herausgefunden, wie man das richtig macht.

Es scheint, als würde fragment [3] nicht aus der Ansicht entfernt werden, wenn die Rückseite gedrückt wird. Sie müssen es also manuell tun!

Verwenden Sie zuerst nicht replace (), sondern entfernen und fügen Sie sie separat hinzu. Es scheint, als ob replace () nicht richtig funktioniert.

Der nächste Teil dazu ist das Überschreiben der onKeyDown-Methode und das Entfernen des aktuellen Fragments bei jedem Drücken der Zurück-Taste.

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

}

Hoffe das hilft!

33
Chris Birch

Zunächst einmal danke @Arvis für eine Augenöffnungserklärung.

Ich ziehe für dieses Problem eine andere Lösung als die akzeptierte Antwort vor. Ich mag es nicht, mich mit dem Überschreiben des Rückverhaltens zu beschäftigen, als unbedingt notwendig. Wenn ich versucht habe, Fragmente selbst hinzuzufügen und zu entfernen, ohne dass der Standard-Stapelrückstand beim Drücken des Rückschalters erscheint, habe ich mich selbst in der Hölle des Fragments gefunden. addiere f2 über f1, wenn du es entfernst f1 ruft keine Callback-Methoden wie onResume, onStart usw. auf und das kann sehr unglücklich sein.

Jedenfalls mache ich es so:

Derzeit wird nur Fragment f1 angezeigt.

f1 -> f2

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

nichts Außergewöhnliches hier. In Fragment f2 führt Sie dieser Code zum Fragmentieren von f3.

f2 -> f3

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

Ich bin mir beim Lesen von Dokumenten nicht sicher, ob dies funktionieren sollte. Diese Poping-Transaktionsmethode wird als asynchron bezeichnet, und vielleicht wäre der beste Weg, popBackStackImmediate () aufzurufen. Soweit ich auf meinen Geräten feststellen kann, funktioniert es einwandfrei.

Die besagte Alternative wäre:

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

Hier wird es tatsächlich eine kurze Rückkehr zu f1 geben, bevor auf f3 übergegangen wird.

Dies ist eigentlich alles, was Sie tun müssen, keine Notwendigkeit, das Back-Stack-Verhalten zu überschreiben ...

16

Ich weiß, dass es eine alte Frage ist, aber ich habe das gleiche Problem und korrigiere es so:

Fügen Sie zunächst Fragment1 mit einem Namen (z. B. "Frag1") zu BackStack hinzu:

frag = new Fragment1();

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

Und dann: Wenn Sie zu Fragment1 zurückkehren möchten (auch nachdem Sie 10 Fragmente darüber hinzugefügt haben), rufen Sie einfach popBackStackImmediate mit dem Namen auf:

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

Hoffe es wird jemandem helfen :)

13
Shirane85

Nachdem @Arvis antwortete, entschied ich mich für Dig noch tiefer und ich habe hier einen technischen Artikel darüber geschrieben: http://www.andreabaccega.com/blog/2015/08/16/how-to-avoid-fragments- überlappende-wegen-zu-Rückstau-Albtraum-in-Android/

Für die faulen Entwickler. Meine Lösung besteht darin, die Transaktionen immer dem Backstack hinzuzufügen und bei Bedarf (automatisch) eine zusätzliche FragmentManager.popBackStackImmediate() durchzuführen.

Der Code besteht aus wenigen Codezeilen, und in meinem Beispiel wollte ich von C nach A springen, ohne zu "B" zu springen, wenn der Benutzer nicht tiefer in den Backstack gegangen ist (z. B. navigiert C zu D).

Der beigefügte Code würde daher wie folgt funktionieren A -> B -> C (zurück) -> A & A -> B -> C -> D (zurück) -> C (zurück) -> B (zurück) -> A

woher 

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

wurden wie in der Frage von "B" bis "C" ausgegeben.

Ok, Ok, hier ist der Code :)

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

Wenn Sie mit addToBackStack () & popBackStack () kämpfen, verwenden Sie einfach

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

Finden Sie in Ihrer Aktivität In OnBackPressed () heraus, was Sie von Tag zu Tag finden und erledigen Sie dann Ihre Sachen

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

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

Weitere Informationen https://github.com/DattaHujare/NavigationDrawer Ich verwende niemals addToBackStack () zur Behandlung von Fragmenten.

1
Dattu Hujare

Ich hatte ein ähnliches Problem, bei dem ich drei aufeinanderfolgende Fragmente in derselben Activity [M1.F0] -> [M1.F1] -> [M1.F2] hatte, gefolgt von einem Aufruf an eine neue Activity [M2]. Wenn der Benutzer eine Taste in [M2] drückte, wollte ich zu [M1, F1] anstelle von [M1, F2] zurückkehren, wie es bereits beim Backpress-Verhalten der Fall war.

Um dies zu erreichen, entferne ich [M1, F2], rufe show auf [M1, F1] auf, übergebe die Transaktion und füge [M1, F2] hinzu, indem ich sie mit hide wiederaufrufe. Dadurch wurde die zusätzliche Hinterpressung entfernt, die sonst zurückgelassen worden wäre.

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

Hallo Nachdem Sie diesen Code ausgeführt haben: Ich kann den Wert von Fragment2 nicht sehen, wenn Sie die Zurück-Taste drücken. Mein Code:

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
Iñaqui

executePendingTransactions(), commitNow() hat nicht funktioniert (

Arbeitete in 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);
    }
}
0
tim4dev

Ich denke, wenn ich deine Geschichte lese, ist [3] auch im Hintergrund. Dies erklärt, warum Sie es aufblitzen sehen.

Die Lösung wäre, niemals [3] auf dem Stack zu setzen.

0
jdekeij