Comment reprendre Fragment depuis BackStack s'il existe

151

J'apprends à utiliser des fragments. J'ai trois instances de Fragmentqui sont initialisées en haut de la classe. J'ajoute le fragment à une activité comme celle-ci:

Déclaration et initialisation:

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 enregistré sans problème dans la pile arrière.

Donc, quand je lance A, Cet puis B, la pile ressemble à ceci:

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

Et lorsque j'appuie sur le bouton «retour», il Best détruit et Creprend.

Mais, lorsque je lance fragment Aune deuxième fois, au lieu de reprendre à partir de la pile arrière, il est ajouté en haut de la pile arrière

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

Mais je veux reprendre Aet détruire tous les fragments dessus (le cas échéant). En fait, j'aime juste le comportement par défaut de la pile arrière.

Comment faire cela?

Attendu: ( Adevrait être repris et les fragments supérieurs devraient ê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, puis que je Bappuie sur «retour», il Best supprimé et Arepris. et en appuyant une deuxième fois sur «retour», vous devez quitter l'application. Mais il montre une fenêtre vide et je dois appuyer une troisième fois pour la fermer.

Aussi, quand je lance A, alors B, puis C, Bencore ...

Attendu:

| |
| |
|B|
|A|
___

Réel:

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

Dois-je remplacer onBackPressed()par une personnalisation ou est-ce que je manque quelque chose?

Kaidul
la source

Réponses:

279

Lire la documentation , il existe un moyen de faire apparaître la pile arrière en fonction du nom de la transaction ou de l'ID fourni par commit. L'utilisation du nom peut être plus facile car cela ne devrait pas nécessiter de garder une trace d'un nombre qui peut changer et renforce la logique «d'entrée unique dans la pile arrière».

Puisque vous ne voulez qu'une seule entrée de pile arrière par Fragment, faites du nom de l'état arrière le nom de classe du fragment (via getClass().getName()). Ensuite, lors du remplacement de a Fragment, utilisez la popBackStackImmediate()méthode. Si elle renvoie true, cela signifie qu'il existe une instance du fragment dans la pile arrière. Sinon, exécutez réellement la logique de remplacement de 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();
  }
}

ÉDITER

Le problème est que lorsque je lance A puis B, puis que j'appuie sur le bouton Retour, B est supprimé et A est repris. et appuyez à nouveau sur le bouton de retour pour quitter l'application. Mais il montre une fenêtre vide et nécessite une autre pression pour la fermer.

En effet, le FragmentTransactionest ajouté à la pile arrière pour nous assurer que nous pouvons afficher les fragments par-dessus plus tard. Une solution rapide pour cela consiste à remplacer onBackPressed()et terminer l'activité si la pile arrière ne contient que 1Fragment

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

En ce qui concerne les entrées de pile arrière en double, votre instruction conditionnelle qui remplace le fragment s'il n'a pas été sauté est clairement différente de celle de mon extrait de code d'origine. Ce que vous faites, c'est ajouter à la pile arrière, que la pile arrière ait été ou non sautée.

Quelque chose comme celui-ci 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é légèrement modifié car la sélection du même fragment alors qu'il était visible a également provoqué des entrées en double.

La mise en oeuvre:

Je suggère fortement de ne pas prendre la mise à jour replaceFragment() séparer la méthode comme vous l'avez fait dans votre code. Toute la logique est contenue dans cette méthode et les pièces mobiles peuvent causer des problèmes.

Cela signifie que vous devez copier la replaceFragment()méthode mise à jour dans votre classe puis changer

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

MODIFIER # 2

Pour mettre à jour le tiroir lorsque la pile arrière change, créez une méthode qui accepte dans un fragment et compare les noms de classe. Si quelque chose correspond, modifiez le titre et la sélection. Ajoutez également unOnBackStackChangedListener et demandez-lui d'appeler votre méthode de mise à jour s'il existe un fragment valide.

Par exemple, dans les activités onCreate(), ajoutez

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ètent le visible Fragment.

A - C
la source
Merci pour votre réponse. :) Cela fonctionne partiellement. J'ai édité ma question au fur et à mesure. S'il vous plaît jeter un oeil)
Kaidul
2
@RishabhSrivastava garde à l'esprit qu'un "pop" détruit généralement le fragment le plus haut. Quant à la méthode appelée en premier, cela dépend - jetez un œil au cycle de vie des fragments. Pour votre cas, j'utiliserais un OnBackstackChangedListenerafin que vous puissiez garantir que vous avez affaire au bon fragment.
A - C
2
@RishabhSrivastava C'est pourquoi j'ai également suggéré le OnBackstackChangedListener.
A - C
1
@AlexanderFarber Vous avez raison. À l'époque où j'ai fait cette réponse, je ne pensais pas instanceof:)
A - C
2
Votre solution sonne bien, mais si vous avez trois fragments et que vous cliquez dessus A-B-C-Bet appuyez une fois en arrière, vous reviendrez Aet non pas C. En effet, popBackStackImmediatenon seulement la balise donnée apparaît mais également tout ce qui précède. Y a-t-il du travail là-bas?
Raeglan
10

Je pense que cette méthode résout 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

Erluxman
la source
S'il vous plaît laissez-moi savoir si une réponse comme celle-ci est contraire aux règles que je voulais simplement que les gens voient facilement dans ce message. Merci ..
erluxman
Je ne sais pas si cela va à l'encontre des règles (probablement pas), mais c'est vraiment utile
Kathir
1
A quoi sert la troisième ligne? -> manager.findFragmentByTag (balise); On dirait qu'il ne fait rien ..
Rik van Velzen
3

Étape 1: Implémentez 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 un 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();
Vinod Joshi
la source
2

Je sais qu'il est assez tard pour répondre à cette question, mais j'ai résolu ce problème par 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();
    }
}
Vivek Pratap Singh
la source
1
@ X-Black ... BaseFragment est une classe de base pour chaque fragment utilisé dans l'application.
Vivek Pratap Singh
0
getFragmentManager().addOnBackStackChangedListener(new FragmentManager.OnBackStackChangedListener() {

    @Override
    public void onBackStackChanged() {

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

Une solution plus simple changera cette ligne

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

Et dans votre mise en page XML, veuillez utiliser

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

Clickable signifie qu'il peut être cliqué par un dispositif de pointeur ou être touché par un dispositif tactile.

Focusablesignifie qu'il peut obtenir la mise au point à partir d'un périphérique d'entrée comme un clavier. Les périphériques d'entrée tels que les claviers ne peuvent pas décider à quelle vue envoyer ses événements d'entrée en fonction des entrées elles-mêmes, ils les envoient donc à la vue qui a le focus.

Hossam Hassan
la source