166 votes

Comment reprendre un fragment existant depuis BackStack ?

J'apprends à utiliser les fragments. J'ai trois instances de Fragment qui sont initialisés en haut de la classe. J'ajoute le fragment à une activité comme ceci :

Déclarer et initialiser :

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

Remplacement/Ajout :

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

Ces extraits fonctionnent correctement. Chaque fragment est attaché à l'activité, et est sauvegardé dans la pile arrière sans aucun problème.

Donc quand je lance A , C et ensuite B la pile ressemble à ceci :

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

Et quand j'appuie sur le bouton "retour", B est détruit et C est repris.

Mais, quand je lance le fragment A une deuxième fois, au lieu de reprendre à partir de la pile arrière, il est ajouté au sommet de la pile arrière

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

Mais je veux reprendre A et détruire tous les fragments qui se trouvent au-dessus (s'il y en a). En fait, j'aime juste le comportement par défaut de la pile arrière.

Comment puis-je y parvenir ?

Attendu : ( A doit être repris et les fragments supérieurs doivent être détruits)

| |
| |
| |
|A|
___

Edit : (suggéré par A--C)

Voici mon code d'essai :

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);
    }

Le problème est que, lorsque je lance A et ensuite B puis appuyez sur "retour", B est supprimé et A Le fait d'appuyer une seconde fois sur la touche "retour" permet de quitter l'application. Mais elle affiche une fenêtre vide et je dois appuyer une troisième fois sur "retour" pour la fermer.

Aussi, quand je lance A alors B alors C alors B encore...

Attendu :

| |
| |
|B|
|A|
___

Réel :

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

Dois-je passer outre onBackPressed() avec une quelconque personnalisation ou est-ce que je rate quelque chose ?

310voto

A--C Points 20292

Lire le documentation Si l'on utilise le nom de la transaction ou l'identifiant fourni par commit, il existe un moyen d'ouvrir la pile arrière. En utilisant le nom mai est plus facile car il ne devrait pas être nécessaire de suivre un nombre qui peut changer et renforce la logique de "l'entrée unique de la pile arrière".

Puisque vous ne voulez qu'une seule entrée de pile arrière par Fragment le nom de l'état arrière doit être le nom de la classe du fragment (via le bouton getClass().getName() ). Ensuite, lorsque l'on remplace un Fragment utilisez le popBackStackImmediate() méthode. Si elle renvoie vrai, cela signifie qu'il y a une instance du Fragment dans la pile arrière. Si ce n'est pas le cas, exécutez réellement la logique de remplacement du Fragment.

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

EDIT

Le problème est le suivant : lorsque je lance A et ensuite B, puis que j'appuie sur le bouton Retour, B est supprimé et A est repris. B est supprimé et A est repris. Et le fait d'appuyer à nouveau sur le bouton de retour devrait quitter l'application. Mais une fenêtre vide s'affiche et il faut appuyer une nouvelle fois sur le bouton pour la fermer.

Cela s'explique par le fait que le FragmentTransaction est ajouté à la pile arrière afin de s'assurer que nous pourrons placer les fragments au sommet plus tard. Une solution rapide pour cela est de remplacer onBackPressed() et terminer l'Activité si la pile arrière ne contient que 1 Fragment

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

En ce qui concerne la duplication des entrées de la pile arrière, votre instruction conditionnelle qui remplace le fragment s'il n'a pas été extrait est clairement différents que ce qu'est mon extrait de code original. Ce que vous faites, c'est que vous ajoutez des éléments à la pile arrière, que celle-ci ait été ouverte ou non.

Quelque chose comme ceci devrait être plus proche de ce que vous voulez :

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

Le conditionnel a été un peu modifié puisque la sélection du même fragment alors qu'il était visible provoquait également des doublons.

Mise en œuvre :

Je suggère fortement de ne pas prendre la mise à jour replaceFragment() à part comme vous l'avez fait dans votre code. Toute la logique est contenue dans cette méthode et le fait de déplacer des parties peut causer des problèmes.

Cela signifie que vous devez copier la mise à jour replaceFragment() dans votre classe, puis modifiez

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

donc c'est simplement

replaceFragment (fragmentName);

EDIT #2

Pour mettre à jour le tiroir lorsque la pile arrière change, créez une méthode qui accepte un fragment et compare les noms des classes. Si quelque chose correspond, changez le titre et la sélection. Ajoutez également un OnBackStackChangedListener et faire en sorte qu'il appelle votre méthode de mise à jour s'il y a un Fragment valide.

Par exemple, dans l'activité onCreate() , ajouter

getSupportFragmentManager().addOnBackStackChangedListener(new OnBackStackChangedListener() {

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

  }
});

Et l'autre méthode :

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

Désormais, chaque fois que la pile arrière change, le titre et la position cochée reflèteront le changement visible. Fragment .

14voto

Je pense que cette méthode peut résoudre votre problème :

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

qui a été initialement publié dans Cette question

3voto

Vinod Joshi Points 294

Étape 1 : Implémenter une interface avec votre classe d'activité

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

Étape 2 : Lorsque vous appelez l'autre fragment, ajoutez cette méthode :

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

2voto

Je sais qu'il est un peu tard pour répondre à cette question, mais j'ai résolu ce problème moi-même et j'ai pensé qu'il valait la peine de le partager avec tout le monde.

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

Et dans le onBackPressed()

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

0voto

gushedaoren Points 81
getFragmentManager().addOnBackStackChangedListener(new FragmentManager.OnBackStackChangedListener() {

    @Override
    public void onBackStackChanged() {

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

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X