Come ripristinare frammento da BackStack se esiste


151

Sto imparando come usare i frammenti. Ne ho tre istanze Fragmentinizializzate in cima alla classe. Sto aggiungendo il frammento a un'attività come questa:

Dichiarazione e inizializzazione:

Fragment A = new AFragment();
Fragment B = new BFragment();
Fragment C = new CFragment();

Sostituzione / Aggiunta:

FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
ft.replace(R.id.content_frame, A);
ft.addToBackStack(null);
ft.commit();

Questi frammenti funzionano correttamente. Ogni frammento è associato all'attività e viene salvato nello stack posteriore senza alcun problema.

Quindi quando lancio A, Ce poi B, lo stack si presenta così:

| |
|B|
|C|
|A|
___

E quando premo il pulsante 'indietro', Bviene distrutto e Cripreso.

Ma, quando lancio il frammento Auna seconda volta, invece di riprendere dallo stack posteriore, viene aggiunto nella parte superiore dello stack posteriore

| |
|A|
|C|
|A|
___

Ma voglio riprendere Ae distruggere tutti i frammenti (se ce ne sono). In realtà, mi piace il comportamento predefinito di back stack.

Come posso farlo?

Previsto: ( Adovrebbe essere ripreso e i frammenti superiori dovrebbero essere distrutti)

| |
| |
| |
|A|
___

Modifica: (suggerito da A - C)

Questo è il mio codice di prova:

private void selectItem(int position) {
        Fragment problemSearch = null, problemStatistics = null;
        FragmentManager manager = getSupportFragmentManager();
        FragmentTransaction ft = manager.beginTransaction();
        String backStateName = null;
        Fragment fragmentName = null;
        boolean fragmentPopped = false;
        switch (position) {
        case 0:
            fragmentName = profile;
            break;
        case 1:
            fragmentName = submissionStatistics;
            break;
        case 2:
            fragmentName = solvedProblemLevel;
            break;
        case 3:
            fragmentName = latestSubmissions;
            break;
        case 4:
            fragmentName = CPExercise;
            break;
        case 5:
            Bundle bundle = new Bundle();
            bundle.putInt("problem_no", problemNo);
            problemSearch = new ProblemWebView();
            problemSearch.setArguments(bundle);
            fragmentName = problemSearch;
            break;
        case 6:
            fragmentName = rankList;
            break;
        case 7:
            fragmentName = liveSubmissions;
            break;
        case 8:
            Bundle bundles = new Bundle();
            bundles.putInt("problem_no", problemNo);
            problemStatistics = new ProblemStatistics();
            problemStatistics.setArguments(bundles);
            fragmentName = problemStatistics;
        default:
            break;
        }
        backStateName = fragmentName.getClass().getName();
        fragmentPopped = manager.popBackStackImmediate(backStateName, 0);
        if (!fragmentPopped) {
            ft.replace(R.id.content_frame, fragmentName);
        }
        ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
        ft.addToBackStack(backStateName);
        ft.commit();

        // I am using drawer layout
        mDrawerList.setItemChecked(position, true);
        setTitle(title[position]);
        mDrawerLayout.closeDrawer(mDrawerList);
    }

Il problema è che quando avvio Ae Bpoi premo 'indietro', Bviene rimosso e Aripreso. e premendo 'indietro' una seconda volta si dovrebbe uscire dall'app. Ma mostra una finestra vuota e devo premere una terza volta per chiuderla.

Inoltre, quando lancio A, quindi B, poi C, poi di Bnuovo ...

Previsto:

| |
| |
|B|
|A|
___

Effettivo:

| |
|B|
|B|
|A|
___

Dovrei ignorare onBackPressed()qualsiasi personalizzazione o mi sto perdendo qualcosa?

Risposte:


279

Leggendo la documentazione , esiste un modo per far apparire lo stack posteriore in base al nome della transazione o all'ID fornito da commit. L'uso del nome può essere più semplice poiché non dovrebbe richiedere di tenere traccia di un numero che può cambiare e rafforzare la logica "entry back stack unica".

Poiché si desidera una sola voce di back stack per Fragment, impostare il nome dello stato posteriore come nome della classe del frammento (via getClass().getName()). Quindi, quando si sostituisce a Fragment, utilizzare il popBackStackImmediate()metodo Se restituisce true, significa che esiste un'istanza del frammento nello stack posteriore. In caso contrario, eseguire effettivamente la logica di sostituzione del frammento.

private void replaceFragment (Fragment fragment){
  String backStateName = fragment.getClass().getName();

  FragmentManager manager = getSupportFragmentManager();
  boolean fragmentPopped = manager.popBackStackImmediate (backStateName, 0);

  if (!fragmentPopped){ //fragment not in back stack, create it.
    FragmentTransaction ft = manager.beginTransaction();
    ft.replace(R.id.content_frame, fragment);
    ft.addToBackStack(backStateName);
    ft.commit();
  }
}

MODIFICARE

Il problema è: quando avvio A e poi B, quindi premo il pulsante Indietro, B viene rimosso e A viene ripreso. e premendo nuovamente il pulsante Indietro dovrebbe uscire dall'app. Ma mostra una finestra vuota e serve un'altra pressione per chiuderla.

Ciò è dovuto al fatto che FragmentTransactionviene aggiunto allo stack posteriore per essere sicuri di poter visualizzare i frammenti in un secondo momento. Una soluzione rapida per questo è l'override onBackPressed()e il completamento dell'attività se lo stack posteriore contiene solo 1Fragment

@Override
public void onBackPressed(){
  if (getSupportFragmentManager().getBackStackEntryCount() == 1){
    finish();
  }
  else {
    super.onBackPressed();
  }
}

Per quanto riguarda le voci duplicate dello stack posteriore, l'istruzione condizionale che sostituisce il frammento se non è stato visualizzato è chiaramente diversa da quella del mio frammento di codice originale. Quello che stai facendo è aggiungere allo stack posteriore indipendentemente dal fatto che lo stack posteriore sia stato espulso o meno.

Qualcosa del genere dovrebbe essere più vicino a quello che vuoi:

private void replaceFragment (Fragment fragment){
  String backStateName =  fragment.getClass().getName();
  String fragmentTag = backStateName;

  FragmentManager manager = getSupportFragmentManager();
  boolean fragmentPopped = manager.popBackStackImmediate (backStateName, 0);

  if (!fragmentPopped && manager.findFragmentByTag(fragmentTag) == null){ //fragment not in back stack, create it.
    FragmentTransaction ft = manager.beginTransaction();
    ft.replace(R.id.content_frame, fragment, fragmentTag);
    ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
    ft.addToBackStack(backStateName);
    ft.commit();
  } 
}

Il condizionale è stato leggermente modificato poiché la selezione dello stesso frammento mentre era visibile ha causato anche voci duplicate.

Implementazione:

Consiglio vivamente di non replaceFragment()smontare il metodo aggiornato come hai fatto nel tuo codice. Tutta la logica è contenuta in questo metodo e lo spostamento di parti può causare problemi.

Questo significa che dovresti copiare il replaceFragment()metodo aggiornato nella tua classe e poi cambiare

backStateName = fragmentName.getClass().getName();
fragmentPopped = manager.popBackStackImmediate(backStateName, 0);
if (!fragmentPopped) {
            ft.replace(R.id.content_frame, fragmentName);
}
ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
ft.addToBackStack(backStateName);
ft.commit();

così è semplicemente

replaceFragment (fragmentName);

EDIT # 2

Per aggiornare il drawer quando lo stack posteriore cambia, crea un metodo che accetta in un frammento e confronta i nomi delle classi. Se qualcosa corrisponde, cambia il titolo e la selezione. Inoltre aggiungi un OnBackStackChangedListenere chiedi al tuo metodo di aggiornamento se c'è un frammento valido.

Ad esempio, in Attività onCreate(), aggiungi

getSupportFragmentManager().addOnBackStackChangedListener(new OnBackStackChangedListener() {

  @Override
  public void onBackStackChanged() {
    Fragment f = getSupportFragmentManager().findFragmentById(R.id.content_frame);
    if (f != null){
      updateTitleAndDrawer (f);
    }

  }
});

E l'altro metodo:

private void updateTitleAndDrawer (Fragment fragment){
  String fragClassName = fragment.getClass().getName();

  if (fragClassName.equals(A.class.getName())){
    setTitle ("A");
    //set selected item position, etc
  }
  else if (fragClassName.equals(B.class.getName())){
    setTitle ("B");
    //set selected item position, etc
  }
  else if (fragClassName.equals(C.class.getName())){
    setTitle ("C");
    //set selected item position, etc
  }
}

Ora, ogni volta che lo stack posteriore cambia, il titolo e la posizione selezionata riflettono il visibile Fragment.


Grazie per la tua risposta. :) Funziona parzialmente. Ho modificato la mia domanda con progressi. Si prega di dare un'occhiata)
Kaidul

2
@RishabhSrivastava tieni presente che un "pop" di solito distrugge il frammento più alto. Per quanto riguarda quale metodo viene chiamato per primo, dipende - dai un'occhiata al ciclo di vita dei frammenti. Per il tuo caso, userei un OnBackstackChangedListenermodo che tu possa garantire che hai a che fare con il giusto frammento.
A - C

2
@RishabhSrivastava Ecco perché ho suggerito OnBackstackChangedListeneranche quello.
A - C

1
@AlexanderFarber Hai ragione. Quando ho fatto questa risposta non stavo pensando instanceof:)
A - C

2
La tua soluzione suona bene, ma se hai tre frammenti e fai clic su di essi A-B-C-Be premi una volta indietro, tornerai indietro Ae non indietro C. Questo perché popBackStackImmediatenon solo viene visualizzato il tag indicato, ma anche tutto quanto sopra. C'è qualche lavoro da queste parti?
Raeglan,

10

Penso che questo metodo possa risolvere il tuo problema:

public static void attachFragment ( int fragmentHolderLayoutId, Fragment fragment, Context context, String tag ) {


    FragmentManager manager = ( (AppCompatActivity) context ).getSupportFragmentManager ();
    FragmentTransaction ft = manager.beginTransaction ();

    if (manager.findFragmentByTag ( tag ) == null) { // No fragment in backStack with same tag..
        ft.add ( fragmentHolderLayoutId, fragment, tag );
        ft.addToBackStack ( tag );
        ft.commit ();
    }
    else {
        ft.show ( manager.findFragmentByTag ( tag ) ).commit ();
    }
}

che è stato originariamente pubblicato in questa domanda


Per favore fatemi sapere se rispondere in questo modo è contro le regole che volevo solo per rendere le persone facili da vedere in questo post. Grazie ..
Erluxman,

Non sono sicuro che sia contro le regole (probabilmente no), ma questo è sicuramente utile
Kathir,

1
A che serve la terza linea? -> manager.findFragmentByTag (tag); Sembra che non stia facendo nulla ..
Rik van Velzen

3

Passaggio 1: implementare un'interfaccia con la classe di attività

public class AuthenticatedMainActivity extends Activity implements FragmentManager.OnBackStackChangedListener{

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        .............
        FragmentManager fragmentManager = getFragmentManager();           
        fragmentManager.beginTransaction().add(R.id.frame_container,fragment, "First").addToBackStack(null).commit();
    }

    private void switchFragment(Fragment fragment){            
      FragmentManager fragmentManager = getFragmentManager();
      fragmentManager.beginTransaction()
        .replace(R.id.frame_container, fragment).addToBackStack("Tag").commit();
    }

    @Override
    public void onBackStackChanged() {
    FragmentManager fragmentManager = getFragmentManager();

    System.out.println("@Class: SummaryUser : onBackStackChanged " 
            + fragmentManager.getBackStackEntryCount());

    int count = fragmentManager.getBackStackEntryCount();

    // when a fragment come from another the status will be zero
    if(count == 0){

        System.out.println("again loading user data");

        // reload the page if user saved the profile data

        if(!objPublicDelegate.checkNetworkStatus()){

            objPublicDelegate.showAlertDialog("Warning"
                    , "Please check your internet connection");

        }else {

            objLoadingDialog.show("Refreshing data..."); 

            mNetworkMaster.runUserSummaryAsync();
        }

        // IMPORTANT: remove the current fragment from stack to avoid new instance
        fragmentManager.removeOnBackStackChangedListener(this);

    }// end if
   }       
}

Passaggio 2: quando si chiama un altro frammento, aggiungere questo metodo:

String backStateName = this.getClass().getName();

FragmentManager fragmentManager = getFragmentManager();
fragmentManager.addOnBackStackChangedListener(this); 

Fragment fragmentGraph = new GraphFragment();
Bundle bundle = new Bundle();
bundle.putString("graphTag",  view.getTag().toString());
fragmentGraph.setArguments(bundle);

fragmentManager.beginTransaction()
.replace(R.id.content_frame, fragmentGraph)
.addToBackStack(backStateName)
.commit();

2

So che è abbastanza tardi per rispondere a questa domanda, ma ho risolto questo problema da solo e ho pensato che valesse la pena condividerlo con tutti

public void replaceFragment(BaseFragment fragment) {
    FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
    final FragmentManager fManager = getSupportFragmentManager();
    BaseFragment fragm = (BaseFragment) fManager.findFragmentByTag(fragment.getFragmentTag());
    transaction.setCustomAnimations(R.anim.enter_from_right, R.anim.exit_to_left, R.anim.enter_from_left, R.anim.exit_to_right);

    if (fragm == null) {  //here fragment is not available in the stack
        transaction.replace(R.id.container, fragment, fragment.getFragmentTag());
        transaction.addToBackStack(fragment.getFragmentTag());
    } else { 
        //fragment was found in the stack , now we can reuse the fragment
        // please do not add in back stack else it will add transaction in back stack
        transaction.replace(R.id.container, fragm, fragm.getFragmentTag()); 
    }
    transaction.commit();
}

E in onBackPressed ()

 @Override
public void onBackPressed() {
    if(getSupportFragmentManager().getBackStackEntryCount()>1){
        super.onBackPressed();
    }else{
        finish();
    }
}

1
@ X-Black ... BaseFragment è una classe di base per ogni frammento utilizzato nell'applicazione.
Vivek Pratap Singh,

0
getFragmentManager().addOnBackStackChangedListener(new FragmentManager.OnBackStackChangedListener() {

    @Override
    public void onBackStackChanged() {

        if(getFragmentManager().getBackStackEntryCount()==0) {
            onResume();
        }    
    }
});

0

Una soluzione più semplice cambierà questa linea

ft.replace(R.id.content_frame, A); per ft.add(R.id.content_frame, A);

E all'interno del layout XML si prega di utilizzare

  android:background="@color/white"
  android:clickable="true"
  android:focusable="true"

Clickable significa che può essere cliccato da un dispositivo puntatore o essere toccato da un dispositivo touch.

Focusablesignifica che può ottenere lo stato attivo da un dispositivo di input come una tastiera. I dispositivi di input come le tastiere non possono decidere a quale vista inviare i propri eventi di input in base agli input stessi, quindi li inviano alla vista che ha lo stato attivo.

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.