J'ai un gros problème avec la façon dont fonctionne le backstack de fragments Android et je serais très reconnaissant pour toute aide offerte.
Imaginez que vous avez 3 fragments
[1] [2] [3]
Je veux que l'utilisateur puisse naviguer [1] > [2] > [3]
mais au retour (en appuyant sur le bouton 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 introduit fragment [2]
dans le détenteur de fragment défini en XML.
La réalité semble être que si je ne veux pas que [2]
réapparaisse lorsque l'utilisateur appuie sur le bouton précédent sur [3]
, je ne dois pas appeler addToBackStack
dans la transaction qui affiche le fragment [3]
. Cela semble complètement contre-intuitif (venant peut-être du monde iOS).
Quoi qu'il en soit, si je le fais de cette façon, lorsque je passe de [1] > [2]
et que j'appuie en arrière, je reviens à [1]
comme prévu.
Si je vais [1] > [2] > [3]
et que j'appuie de nouveau sur je retourne à [1]
(comme prévu) . Maintenant, le comportement étrange se produit lorsque je tente de sauter à [2]
à partir de [1]
. Tout d'abord, [3]
est brièvement affiché avant que [2]
ne soit visible. Si j'appuie en arrière à ce moment-là, [3]
s'affiche et si j'appuie de nouveau en arrière, l'application se ferme.
Quelqu'un peut-il m'aider à comprendre ce qui se passe ici?
Et voici le fichier XML de mise en page de 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"
/>
Update C'est le code que j'utilise pour construire par la hiérarchie de navigation
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
Si nous gardons à l'esprit que .replace()
est égal à .remove().add()
, nous le savons par la documentation:
Remplacer un fragment existant qui a été ajouté à un conteneur. Ceci est essentiellement identique à l'appel de
remove(Fragment)
pour tous les fragments actuellement ajoutés qui ont été ajoutés avec les mêmescontainerViewId
et ensuiteadd(int, Fragment, String)
avec les mêmes arguments donnés ici.
alors ce qui se passe est comme ça (j'ajoute des chiffres au fragment 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 tout ce qui est trompeur commence à arriver)
Rappelez-vous que .addToBackStack()
enregistre uniquement transaction et non le fragment en tant que tel! 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 >
Pensez à implémenter FragmentManager.BackStackChangedListener
pour surveiller les modifications dans la pile arrière et appliquer votre logique dans onBackStackChanged()
methode:
FragmentTransaction.addToBackStack(String name);
Droite!!! Après avoir tiré beaucoup de poils, j'ai enfin trouvé comment le faire fonctionner correctement.
Il semble que fragment [3] ne soit pas supprimé de la vue lorsque vous appuyez sur la touche Retour, vous devez donc le faire manuellement!
Tout d’abord, n’utilisez pas replace (), mais utilisez remove et add séparément. Il semble que replace () ne fonctionne pas correctement.
La partie suivante consiste à remplacer la méthode onKeyDown et à supprimer le fragment en cours chaque fois que le bouton Précédent est enfoncé.
@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();
}
J'espère que cela t'aides!
Tout d’abord, merci à @Arvis pour une explication qui ouvre les yeux.
Je préfère solution différente à la réponse acceptée ici pour ce problème. Je n'aime pas jouer avec le comportement de retour en arrière plus qu'absolument nécessaire et quand j'ai essayé d'ajouter et de supprimer des fragments sans mon retour de pile par défaut lorsque le bouton Précédent est enfoncé, je me suis retrouvé dans l'enfer des fragments :) ajoutez f2 sur f1 lorsque vous le supprimez, f1 n'appelle aucune méthode de rappel telle que onResume, onStart, etc., ce qui peut être très malheureux.
Quoi qu'il en soit, voici comment je le fais:
Présentement affiché n'est que le 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 fragment F2, ce code vous amène à fragmenter F3.
f2 -> f3
Fragment3 f3 = new Fragment3();
getActivity().getSupportFragmentManager().popBackStack();
getActivity().getSupportFragmentManager().beginTransaction().replace(R.id.main_content, f3).addToBackStack(null).commit();
En lisant la documentation, je ne suis pas sûr que cela fonctionne, il est dit que cette méthode de transaction contextuelle est asynchrone, et un meilleur moyen serait d'appeler popBackStackImmediate (). Mais autant que je sache sur mes appareils, cela fonctionne parfaitement.
Cette alternative serait:
final FragmentActivity activity = getActivity();
activity.getSupportFragmentManager().popBackStackImmediate();
activity.getSupportFragmentManager().beginTransaction().replace(R.id.main_content, f3).addToBackStack(null).commit();
En fait, ici, il sera bref de revenir à F1 avant de passer à F3, il ya donc un léger problème.
C’est tout ce que vous avez à faire, pas besoin de redéfinir 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épare comme ceci:
Commencez par ajouter Fragment1 à BackStack avec un nom (par exemple, "Frag1"):
frag = new Fragment1();
transaction = getSupportFragmentManager().beginTransaction();
transaction.replace(R.id.detailFragment, frag);
transaction.addToBackStack("Frag1");
transaction.commit();
Et ensuite, chaque fois que vous souhaitez revenir à Fragment1 (même après avoir ajouté 10 fragments au-dessus), appelez simplement popBackStackImmediate avec le nom suivant:
getSupportFragmentManager().popBackStackImmediate("Frag1", 0);
J'espère que ça va aider quelqu'un :)
Après la réponse de @Arvis, j’ai décidé de creuser encore plus profondément et j’ai écrit un article technique à ce sujet ici: http://www.andreabaccega.com/blog/2015/08/16/how-to-avoid-fragments- chevauchement-dû-à-backstack-cauchemar sous Android/
Pour les développeurs paresseux autour. Ma solution consiste à toujours ajouter les transactions au backstack et à effectuer un FragmentManager.popBackStackImmediate()
supplémentaire en cas de besoin (automatiquement).
Le code contient très peu de lignes de code et, dans mon exemple, je voulais passer de C à A sans revenir à "B" si l'utilisateur ne s'enfonçait pas plus profondément dans le panier (par exemple, de C navigue vers D).
Par conséquent, le code 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()
sont passé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 vous battez avec addToBackStack () & popBackStack (), utilisez simplement
FragmentTransaction ft =getSupportFragmentManager().beginTransaction();
ft.replace(R.id.content_frame, new HomeFragment(), "Home");
ft.commit();`
Dans votre activité dans OnBackPressed (), recherchez le segment par tag puis faites votre travail
Fragment home = getSupportFragmentManager().findFragmentByTag("Home");
if (home instanceof HomeFragment && home.isVisible()) {
// do you stuff
}
Pour plus d'informations https://github.com/DattaHujare/NavigationDrawer Je n'utilise jamais addToBackStack () pour gérer les fragments.
J'ai eu un problème similaire où j'avais 3 fragments consécutifs dans la même Activity
[M1.F0] -> [M1.F1] -> [M1.F2] suivi d'un appel à une nouvelle Activity
[M2]. Si l'utilisateur appuyait sur un bouton dans [M2], je souhaitais revenir à [M1, F1] au lieu de [M1, F2], ce qui correspond déjà au comportement de la presse arrière.
Pour ce faire, je supprime [M1, F2], appelle show sur [M1, F1], valide la transaction, puis rajoute [M1, F2] en l'appelant par hide. Cela a supprimé la presse arrière supplémentaire qui aurait autrement été laissée.
// 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 parviens pas à 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();
}
executePendingTransactions()
, commitNow()
non travaillé (
Travaillé dans androidx (jetpack).
private final FragmentManager fragmentManager = getSupportFragmentManager();
public void removeFragment(FragmentTag tag) {
Fragment fragmentRemove = fragmentManager.findFragmentByTag(tag.toString());
if (fragmentRemove != null) {
fragmentManager.beginTransaction()
.remove(fragmentRemove)
.commit();
// fix by @Ogbe
fragmentManager.popBackStackImmediate(tag.toString(),
FragmentManager.POP_BACK_STACK_INCLUSIVE);
}
}
Je pense que, quand j'ai lu votre histoire, [3] se trouve également sur le tapis. Cela explique pourquoi vous le voyez clignoter.
La solution serait de ne jamais définir [3] sur la pile.