Problemi con lo stack posteriore di Android Fragment


121

Ho un enorme problema con il modo in cui sembra funzionare il backstack di frammenti Android e sarei molto grato per qualsiasi aiuto offerto.

Immagina di avere 3 frammenti

[1] [2] [3]

Voglio che l'utente sia in grado di navigare [1] > [2] > [3]ma sulla via del ritorno (premendo il pulsante Indietro) [3] > [1].

Come avrei immaginato, ciò sarebbe stato ottenuto non chiamando addToBackStack(..)durante la creazione della transazione che porta il frammento [2]nel contenitore del frammento definito in XML.

La realtà di questo sembra che se non voglio [2]apparire di nuovo quando l'utente preme il pulsante Indietro [3], non devo chiamare addToBackStackla transazione che mostra frammento [3]. Questo sembra del tutto controintuitivo (forse proveniente dal mondo iOS).

Ad ogni modo, se lo faccio in questo modo, quando vado da [1] > [2]e premo indietro torno a [1]come previsto.

Se vado [1] > [2] > [3]e poi premo indietro, salto indietro [1](come previsto). Ora lo strano comportamento si verifica quando provo a saltare di [2]nuovo da [1]. Prima di tutto [3]viene visualizzato brevemente prima che [2]venga visualizzato. Se premo Indietro a questo punto [3]viene visualizzato e se premo ancora una volta l'app esce.

Qualcuno può aiutarmi a capire cosa sta succedendo qui?


Ed ecco il file xml di layout per la mia attività principale:

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

        />



Aggiorna Questo è il codice che sto usando per creare tramite la gerarchia nav

    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]

Grazie molto


ma il frammento si sovrappone, quando eseguo la stampa dal frammento C al frammento A.
Priyanka

ho lo stesso problema, come lo risolvi?
Priyanka

riferisci il mio ans .. potrebbe essere d'aiuto < stackoverflow.com/questions/14971780/… >
MD Khali

Risposte:


203

Spiegazione: su cosa sta succedendo qui?

Se teniamo presente che .replace()è uguale a .remove().add()quello che sappiamo dalla documentazione:

Sostituisci un frammento esistente che è stato aggiunto a un contenitore. Questo è essenzialmente lo stesso che richiedere remove(Fragment)tutti i frammenti attualmente aggiunti che sono stati aggiunti con lo stesso containerViewIde quindi add(int, Fragment, String)con gli stessi argomenti forniti qui.

allora quello che sta succedendo è così (aggiungo numeri al frammento per renderlo più chiaro):

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

(qui iniziano a succedere tutte le cose fuorvianti)

Ricorda che .addToBackStack()salva solo la transazione, non il frammento come se stesso! Quindi ora abbiamo frag3sul 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 >

Possibile soluzione

Considera l'implementazione FragmentManager.BackStackChangedListenerper osservare i cambiamenti nello stack indietro e applicare la tua logica nel onBackStackChanged()metodo:


Buona spiegazione @arvis. Tuttavia, come possiamo prevenire questo comportamento senza ricorrere a modi hacker come quello di DexterMoon o quello di Nemanja usando popBackStack che mostra il frammento durante la riproduzione dell'animazione di transizione?
momo

@momo potresti implementare FragmentManager.BackStackChangedListenerper controllare le modifiche allo stack posteriore. Monitora tutte le tue transazioni con onBackStackChanged()methode e agisci come necessario: ad es. tracciare un conteggio delle transazioni in BackStack; controlla la transazione specifica per nome ( FragmentTransaction addToBackStack (String name)) ecc.
Arvis

Grazie per aver risposto. In realtà l'ho provato oggi, registrato l'ascoltatore e rimosso il frammento da onBackstackChange. Mentre viene riprodotta la transizione scoppiettante, il frammento diventa un'area bianca vuota. Immagino che il metodo venga attivato quando lo scoppio avvia l'animazione e non quando finisce ...
momo

4
Evita di usare le pile posteriori! non aiuta davvero con l'efficienza complessiva! usa plain replace () o ancora meglio rimuovi / aggiungi ogni volta che vuoi navigare!
stack_ved il

@Arvis puoi aiutarmi a ottenere lo stesso problema .... conteggio stack 0 ma il mio frammento è ancora visibile?
Erum

33

Destra!!! dopo aver tirato molti capelli ho finalmente capito come farlo funzionare correttamente.

Sembra che il frammento [3] non venga rimosso dalla vista quando si preme il tasto Indietro, quindi devi farlo manualmente!

Prima di tutto, non usare replace () ma usa invece rimuovi e aggiungi separatamente. Sembra che replace () non funzioni correttamente.

La parte successiva è l'override del metodo onKeyDown e la rimozione del frammento corrente ogni volta che viene premuto il pulsante Indietro.

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

}

Spero che questo ti aiuti!


funziona, ma non può essere utilizzato nel mio progetto poiché il frammento viene ricaricato! eventuali suggerimenti?
TharakaNirmana

Ma se lo sto facendo. Ricevo lo schermo vuoto quando torno da C ad A per il frammento C.
Nigam Patro

Sospetto che la risposta di @Arvis dovrebbe far luce sul tuo problema
Chris Birch

16

Prima di tutto grazie @Arvis per una spiegazione che apre gli occhi.

Preferisco una soluzione diversa alla risposta accettata qui per questo problema. Non mi piace scherzare con l'override del comportamento all'indietro più del necessario e quando ho provato ad aggiungere e rimuovere frammenti da solo senza che lo stack di backup predefinito si aprisse quando si preme il pulsante Indietro, mi sono trovato nell'inferno dei frammenti :) Se tu. aggiungi f2 su f1 quando lo rimuovi f1 non chiamerà nessuno dei metodi di callback come onResume, onStart ecc. e questo può essere molto sfortunato.

Comunque questo è come lo faccio:

Attualmente in mostra è solo il frammento f1.

f1 -> f2

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

niente fuori dall'ordinario qui. Rispetto al frammento f2 questo codice ti porta al frammento f3.

f2 -> f3

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

Non sono sicuro leggendo i documenti se questo dovrebbe funzionare, si dice che questo metodo di transazione poping sia asincrono e forse un modo migliore sarebbe chiamare popBackStackImmediate (). Ma per quanto ne so sui miei dispositivi funziona perfettamente.

La suddetta alternativa sarebbe:

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

Qui ci sarà effettivamente un breve ritorno a f1 prima di passare a f3, quindi un leggero glitch lì.

Questo è in realtà tutto ciò che devi fare, non c'è bisogno di sovrascrivere il comportamento dello stack indietro ...


6
ma il problema
tecnico

Questo sembra meno "hack-ey" che ascoltare i cambiamenti di BackStack. Grazie.
ahaisting

13

So che è una vecchia domanda ma ho lo stesso problema e lo risolvo in questo modo:

Innanzitutto, aggiungi Fragment1 a BackStack con un nome (ad esempio "Frag1"):

frag = new Fragment1();

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

E poi, ogni volta che vuoi tornare a Fragment1 (anche dopo aver aggiunto 10 frammenti sopra di esso), chiama popBackStackImmediate con il nome:

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

Spero che possa aiutare qualcuno :)


1
Bella risposta. Ho dovuto usare getSupportFragmentManager (). PopBackStackImmediate ("Frag1", FragmentManager.POP_BACK_STACK_INCLUSIVE); per farlo funzionare per il mio caso d'uso però
eliasbagley

1
Dove devo mettere questo codice ???? getSupportFragmentManager (). popBackStackImmediate ("Frag1", 0); Su MainActivty o onBackPress
pavel

la sua non funziona. :( questo è il mio codice, Nel mio caso Fragment is Overlaping. apre il Fragment A, ma Frgment A si sovrappone al 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

Dopo la risposta di @Arvis ho deciso di scavare ancora più a fondo e ho scritto un articolo tecnico su questo qui: http://www.andreabaccega.com/blog/2015/08/16/how-to-avoid-fragments-overlapping- due-a-backstack-nightmare-in-android /

Per gli sviluppatori pigri in giro. La mia soluzione consiste nell'aggiungere sempre le transazioni al backstack ed eseguire un extra FragmentManager.popBackStackImmediate()quando necessario (automaticamente).

Il codice è composto da pochissime righe di codice e, nel mio esempio, volevo saltare da C ad A senza tornare a "B" se l'utente non fosse andato più in profondità nel backstack (es. Da C naviga in D).

Quindi il codice allegato funzionerebbe come segue A -> B -> C (back) -> A & A -> B -> C -> D (back) -> C (back) -> B (back) -> A

dove

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

sono stati emessi da "B" a "C" come nella domanda.

Ok, Ok ecco il codice :)

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
ottenere un arresto anomalo su questa riga fragmentManager.popBackStackImmediate (); errore: java.lang.IllegalStateException: FragmentManager sta già eseguendo transazioni su com.example.myapplication.FragmentA $ 2.onBackStackChanged (FragmentA.java:43)
Priyanka

1

Se stai lottando con addToBackStack () e popBackStack (), usa semplicemente

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

Nella tua attività in OnBackPressed () scopri fargment per tag e poi fai le tue cose

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

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

Per ulteriori informazioni https://github.com/DattaHujare/NavigationDrawer Non uso mai addToBackStack () per la gestione dei frammenti.


0

Penso, quando ho letto la tua storia, che anche [3] sia nel backstack. Questo spiega perché lo vedi lampeggiare.

La soluzione sarebbe non impostare mai [3] sullo stack.


Ciao jdekei, grazie per il tuo contributo. Il problema è che non riesco a vedere dove sto aggiungendo [3] al backstack. Ho aggiunto un altro pezzo di codice che dimostra (a livello di programmazione) esattamente la navigazione che stavo eseguendo utilizzando i pulsanti.
Chris Birch

Aiuta anche a me, ma io uso solo removeCurFragment dal tuo codice e un po 'un altro controllo dei frammenti. Il metodo di sostituzione per me funziona bene, forse va s old method issue but now itbene. Grazie
Viktor V.

0

Ho avuto un problema simile in cui avevo 3 frammenti consecutivi nello stesso Activity[M1.F0] -> [M1.F1] -> [M1.F2] seguito da una chiamata a un nuovo Activity[M2]. Se l'utente premeva un pulsante in [M2], volevo tornare a [M1, F1] invece di [M1, F2], che è ciò che ha già fatto il comportamento di back press.

Per fare ciò, rimuovo [M1, F2], chiamo show su [M1, F1], effettuo il commit della transazione e poi aggiungo [M1, F2] richiamandolo con hide. Ciò ha rimosso la stampa posteriore extra che altrimenti sarebbe stata lasciata indietro.

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

Ciao Dopo aver fatto questo codice: non riesco a vedere il valore di Fragment2 premendo il tasto Indietro. Il mio codice:

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()non ha funzionato (

Ha funzionato 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);
    }
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.