Problèmes avec la pile de retour de Fragment Android
j'ai un gros problème avec la façon dont le fragment android backsack semble fonctionner et serais très reconnaissant pour toute aide qui 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é cela serait accompli en n'appelant pas addToBackStack(..)
lors de la création de la transaction qui apporte le fragment [2]
dans le support de fragment défini en XML.
la réalité de cela semble comme si si Je ne veux pas que [2]
apparaître à nouveau lorsque l'utilisateur appuie sur le bouton de retour sur [3]
, Je ne dois pas appeler addToBackStack
dans la transaction qui montre fragment [3]
. Cela semble totalement contre-intuitif (peut-être venant du Monde iOS).
de toute façon si je le fais ceci en passant de [1] > [2]
à [1]
, j'arrive à [1]
comme prévu.
si je vais [1] > [2] > [3]
et puis appuyez en arrière je saute en arrière à [1]
(comme prévu).
Maintenant le comportement étrange se produit quand j'essaie de sauter à [2]
de nouveau de [1]
. Tout d'abord [3]
est brièvement affiché avant [2]
vient en vue. Si je presse en arrière à ce point [3]
est affiché, et si je presse en arrière une fois de plus les sorties de l'application.
quelqu'un Peut-il m'aider à comprendre ce qui se passe ici?
Et voici le fichier XML de mise en page 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 hérarchy
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
8 réponses
Explication: sur ce qui se passe ici?
Si nous gardons à l'esprit que .replace()
est égal à .remove().add()
que nous connaissons par la documentation:
remplacer un fragment existant qui a été ajouté à un conteneur. C'est essentiellement la même chose que d'appeler
remove(Fragment)
pour tous les fragments actuellement ajoutés qui ont été ajoutés avec le mêmecontainerViewId
et puisadd(int, Fragment, String)
avec les mêmes arguments donnés ici.
alors ce qui se passe est comme ceci (j'ajoute des nombres au 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 toutes les choses trompeuses commence à se produire)
rappelez-vous que .addToBackStack()
n'enregistre que transaction pas le fragment comme lui-même! Donc 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
envisagez de mettre en œuvre FragmentManager.BackStackChangedListener
pour surveiller les changements dans la pile arrière et appliquer votre logique dans onBackStackChanged()
méthode:
- Trace un compte de transaction;
- vérifier la transaction par nom
FragmentTransaction.addToBackStack(String name);
- etc.
C'est ça!!! après avoir beaucoup tiré mes cheveux, j'ai enfin trouvé comment faire pour que ça marche correctement.
il semble que fragment [3] n'est pas enlevé de la vue lorsque le dos est pressé donc vous devez le faire manuellement!
tout d'abord, n'utilisez pas replace() mais utilisez plutôt remove et add separately. Il semble que remplacer les() ne fonctionne pas correctement.
la prochaine partie de ceci est de passer outre la méthode onKeyDown et de supprimer le actuel 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!
tout d'abord merci @Arvis pour une explication qui ouvre les yeux.
je préfère une solution différente de la réponse acceptée ici pour ce problème. Je n'aime pas jouer avec le comportement de dépassement de dossier plus qu'absolument nécessaire et quand j'ai essayé d'ajouter et d'enlever des fragments sur mon propre sans poping pile de retour par défaut lorsque le bouton de retour est pressé je me suis trouvé dans l'enfer de fragment :) si vous .ajouter f2 sur f1 lorsque vous le supprimez f1 n'appellera aucune des méthodes de rappel comme onResume,onStart etc. et cela peut être très regrettable.
de toute façon, c'est comme ça que je le fais:
actuellement affiché n'est qu'un fragment f1.
f1 - > f2
Fragment2 f2 = new Fragment2();
this.getActivity().getSupportFragmentManager().beginTransaction().replace(R.id.main_content,f2).addToBackStack(null).commit();
rien qui sorte de l'ordinaire ici. Que dans le fragment f2 ce code vous amène au fragment 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 en lisant docs si cela devrait fonctionner, ce poping méthode de transaction est dit asynchrone, et peut-être une meilleure façon serait d'appeler popBackStackImmediate(). Mais pour autant que je sache sur mes appareils, ça marche parfaitement.
, a déclaré alternative serait:
final FragmentActivity activity = getActivity();
activity.getSupportFragmentManager().popBackStackImmediate();
activity.getSupportFragmentManager().beginTransaction().replace(R.id.main_content, f3).addToBackStack(null).commit();
ici il y aura en fait un bref retour au beofre f1 en passant à f3, donc un léger bug là.
c'est en fait tout ce que vous avez à faire, pas besoin de modifier le comportement de la pile arrière...
je sais que c'est une vieille question mais j'ai le même problème et je le règle comme ceci:
D'abord, ajouter Fragment1 à dos avec un nom (E. G "Frag1"):
frag = new Fragment1();
transaction = getSupportFragmentManager().beginTransaction();
transaction.replace(R.id.detailFragment, frag);
transaction.addToBackStack("Frag1");
transaction.commit();
et puis, chaque fois que vous voulez revenir à Fragment1( même après avoir ajouté 10 fragments au-dessus de lui), il suffit d'appeler popBackStackImmediate avec le nom:
getSupportFragmentManager().popBackStackImmediate("Frag1", 0);
j'Espère que ça aidera quelqu'un :)
après la réponse de @Arvis j'ai décidé de creuser encore plus et j'ai écrit un article technique à ce sujet ici: http://www.andreabaccega.com/blog/2015/08/16/how-to-avoid-fragments-overlapping-due-to-backstack-nightmare-in-android /
Pour les paresseux développeurs autour de. Ma solution consiste à toujours ajouter les transactions dans les coulisses et effectuer un FragmentManager.popBackStackImmediate()
supplémentaire lorsque nécessaire (automatiquement).
le code est très peu lignes de code et, dans mon exemple, je voulais passer de C à A sans revenir à "B" si l'utilisateur n'allait pas plus loin dans les coulisses (ex de C navigates à D).
par conséquent, le code ci-joint fonctionnerait comme suit: A - > B - > C (retour) - > A & A - > B - > C - > D (retour) - > C (retour) - > B (retour) - > a
où
fm.beginTransaction().replace(R.id.content, new CFragment()).commit()
ont été publiés de" B "à" C " comme dans la question.
Ok, ok voici le code :)
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();
}
}
}
});
}
si vous êtes aux prises avec addToBackStack () & popBackStack (), alors utilisez simplement
FragmentTransaction ft =getSupportFragmentManager().beginTransaction();
ft.replace(R.id.content_frame, new HomeFragment(), "Home");
ft.commit();`
dans votre activité dans OnBackPressed() découvrez fargment par tag et puis faites vos affaires
Fragment home = getSupportFragmentManager().findFragmentByTag("Home");
if (home instanceof HomeFragment && home.isVisible()) {
// do you stuff
}
pour plus d'Information https://github.com/DattaHujare/NavigationDrawer Je n'utilise jamais addToBackStack() pour gérer fragment.
je pense, quand j'ai lu votre histoire que [3] est aussi dans les coulisses. Cela explique pourquoi vous voyez qu'il clignote.
Solution serait de ne jamais mettre [3] sur la pile.
j'ai eu un problème similaire où j'ai eu 3 fois de suite fragments dans le même Activity
[M1.F0] - > [M1.F1] - > [M1.F2] suivi d'un appel à un nouveau Activity
[M2]. Si l'Utilisateur a appuyé sur un bouton dans [M2] je voulais revenir à [M1,F1] au lieu de [M1,F2] ce qui est ce que le comportement de retour de presse a 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 à cacher. Cela a enlevé la presse arrière supplémentaire qui aurait autrement été laissé 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 Back. 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();
}