125 votes

Problèmes liés à la pile de retour du fragment Android

J'ai un énorme problème avec la façon dont le android fragment backstack semble fonctionner et vous en serais très reconnaissant pour toute aide qui leur est offert.

Imaginez que vous avez 3 Fragments

[1] [2] [3]

Je veux que l'utilisateur puisse naviguer [1] > [2] > [3] mais sur le chemin du retour (appuyer sur le bouton de retour) [3] > [1].

Comme je l'aurais imaginé que cela pourrait se faire en appelant ne pas addToBackStack(..) lors de la création de la transaction qui apporte fragment [2] dans le fragment titulaire défini en XML.

La réalité de ce qui semble bien que si je ne veux pas [2] s'affiche à nouveau lorsque l'utilisateur appuie sur le bouton de retour sur [3], je ne dois pas appeler addToBackStack dans la transaction qui illustre le fragment [3]. Cela semble complètement contre-intuitif (provenant peut-être du monde iOS).

De toute façon si je le fais de cette façon, quand je vais d' [1] > [2] et appuyez sur la touche de retour, j'revenir à l' [1] comme prévu.

Si je pars [1] > [2] > [3] , puis appuyez sur la touche back je saute en arrière pour [1] (comme prévu). Maintenant le comportement étrange se passe lorsque j'essaie et le saut à l' [2] de nouveau à partir de [1]. Tout d'abord [3] s'affiche brièvement avant d' [2] est offert à la vue. Si j'appuyez sur pour revenir à ce point - [3] est affiché, et si j'appuie sur le dos une fois de plus, l'application se ferme.

Quelqu'un peut-il m'aider à comprendre ce qui se passe ici?


Et voici la mise en page du fichier xml pour mon activité 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"

        />



Mise à jour C'est le code que j'utilise pour construire par nav hiérarchie

    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]

Merci beaucoup

205voto

Arvis Points 1945

EXPLICATION (sur ce qui se passe ici?):

Si nous gardons à l'esprit que .replace() = .supprimer().ajouter() (que nous connaissons par la documentation )

Remplacer un autre fragment qui a été ajouté à un conteneur. C'est essentiellement le même que l'appel à la supprimer(Fragment) pour l'ensemble des ajouté fragments qui ont été ajoutés avec la même containerViewId et puis ajouter(int, Fragment, String) avec les mêmes arguments donnés ici.

alors qu'est-ce qui se passe c'est comme ça (je suis d'ajouter des numéros à la frag pour le rendre plus clair):

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

(ici, tous les trompeurs choses commence à se produire)

Rappelez-vous que .addToBackStack() est la sauvegarde des TRANSACTION qui n'est pas le FRAGMENT en tant que lui-même!

Alors maintenant, nous avons frag3 sur la mise en page:

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

Solution Possible:

envisager la mise en œuvre FragmentManager.BackStackChangedListener pour surveiller les modifications de la pile de retour et d'appliquer votre logique dans onBackStackChanged() methode:

  • trace un compte de transaction;
  • vérifier la transaction en particulier par son nom (FragmentTransaction addToBackStack(String name));
  • etc.

33voto

ChrisBirch Points 452

Droite!!!!! après beaucoup de s'arracher les cheveux, j'ai enfin travaillé sur la façon de faire ce travail correctement.

Il semble que le fragment [3] n'est pas supprimé de la vue lorsque l'arrière est activé, de sorte que vous avez à faire manuellement!

Tout d'abord, ne pas utiliser replace (), mais d'utiliser plutôt le supprimer et ajouter séparément. Il semble que remplacer les() ne fonctionne pas correctement.

La prochaine partie de cette substitution de la méthode onKeyDown et retirez le fragment chaque fois que le bouton de retour est pressé.

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

}

Espérons que cette aide!

16voto

Tout d'abord merci @Arvis pour une ouverture des yeux explication.

Je préfère la solution pour la accepté de répondre ici pour ce problème. Je n'aime pas jouer avec prépondérant en retour le comportement de plus que ce qui est absolument nécessaire et quand j'ai essayé d'ajouter et de retirer des fragments de ma propre sans défaut pile de retour poping en arrière quand le bouton est pressé, j'ai trouvé mon auto dans le fragment de l'enfer :) Si vous .ajouter f2 sur f1 lorsque vous la retirez de la f1 ne pas appeler l'une des méthodes de rappel comme onResume, onStart etc. et ça peut être très malheureux.

De toute façon c'est comment je le fais:

Actuellement sur l'affichage est seulement fragment f1.

f1 -> f2

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

rien d'extraordinaire ici. Que dans le fragment de f2 ce code vous emmène à la fragment de f3.

f2 -> f3

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

Je ne suis pas sûr, par la lecture des docs si cela doit fonctionner, ce poping méthode de transaction est dit asynchrone, et peut-être une meilleure façon serait d'appeler popBackStackImmediate(). Mais aussi loin que je peux dire sur mes appareils, cela fonctionne parfaitement.

Ladite solution serait:

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

Ici il y aura effectivement être bref retour à la f1 beofre de passer à f3, donc un léger problème là.

C'est en fait tout ce que vous avez à faire, pas besoin de surcharger pile de retour de comportement...

0voto

jdekeij Points 109

Je pense, quand j'ai lu votre histoire, que [3] est aussi sur le tapis. Cela explique pourquoi vous le voyez clignoter.

La solution serait de ne jamais définir [3] sur la pile.

0voto

I Delgado Points 63

J'ai eu un problème similaire où j'ai eu 3 fois de suite des fragments dans la même activité [M1.F0]->[M1.F1]->[M1.F2], suivi par un appel à une nouvelle activité [M2]. Si l'utilisateur appuie sur un bouton en [M2] je voulais revenir à [M1,F1] au lieu de [M1,F2] qui est ce retour appuyez sur le comportement déjà fait.

Pour ce faire j'ai supprimer [M1,F2], appelez show sur [M1,F1], valider la transaction, puis ajouter [M1,F2] de retour en l'appelant à se cacher. Cette supprimé le dos extra presse qui, autrement, auraient été laissés derriè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();

=======================================================================

Salut Après avoir fait ce code: je ne suis pas en mesure de voir la valeur de Fragment2 en appuyant sur la Touche Retour. Mon 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();
    }

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