Android 4.2: comportement de la pile arrière avec des fragments imbriqués

108

Avec Android 4.2, la bibliothèque de support prend en charge les fragments imbriqués, voir ici . J'ai joué avec et trouvé un comportement / bogue intéressant concernant la pile arrière et getChildFragmentManager () . Lorsque vous utilisez getChildFragmentManager () et addToBackStack (nom de chaîne), en appuyant sur le bouton Précédent, le système ne descend pas de la pile arrière jusqu'au fragment précédent. D'un autre côté, lorsque vous utilisez getFragmentManager () et addToBackStack (String name), en appuyant sur le bouton Précédent, le système revient au fragment précédent.

Pour moi, ce comportement est inattendu. En appuyant sur le bouton de retour de mon appareil, je m'attends à ce que le dernier fragment ajouté à la pile arrière apparaisse, même si le fragment a été ajouté à la pile arrière dans le gestionnaire de fragments pour enfants.

Ce comportement est-il correct? Ce comportement est-il un bogue? Y a-t-il une solution à ce problème?

exemple de code avec getChildFragmentManager ():

public class FragmentceptionActivity extends FragmentActivity {

@Override
protected void onCreate(Bundle arg0) {
    super.onCreate(arg0);

    final FrameLayout wrapper1 = new FrameLayout(this);
    wrapper1.setLayoutParams(new FrameLayout.LayoutParams(
            FrameLayout.LayoutParams.MATCH_PARENT,
            FrameLayout.LayoutParams.MATCH_PARENT));
    wrapper1.setId(1);

    final FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(
            FrameLayout.LayoutParams.MATCH_PARENT,
            FrameLayout.LayoutParams.WRAP_CONTENT);
    params.topMargin = 0;

    final TextView text = new TextView(this);
    text.setLayoutParams(params);
    text.setText("fragment 1");
    wrapper1.addView(text);

    setContentView(wrapper1);

    getSupportFragmentManager().beginTransaction().addToBackStack(null)
            .add(1, new Fragment1()).commit();
}

public class Fragment1 extends Fragment {
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {
        final FrameLayout wrapper2 = new FrameLayout(getActivity());
        wrapper2.setLayoutParams(new FrameLayout.LayoutParams(
                FrameLayout.LayoutParams.MATCH_PARENT,
                FrameLayout.LayoutParams.MATCH_PARENT));
        wrapper2.setId(2);

        final FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(
                FrameLayout.LayoutParams.MATCH_PARENT,
                FrameLayout.LayoutParams.WRAP_CONTENT);
        params.topMargin = 100;

        final TextView text = new TextView(getActivity());
        text.setLayoutParams(params);
        text.setText("fragment 2");
        wrapper2.addView(text);

        return wrapper2;
    }

    @Override
    public void onViewCreated(View view, Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);

        getFragmentManager().beginTransaction().addToBackStack(null)
                .add(2, new Fragment2()).commit();
    }
}

public class Fragment2 extends Fragment {
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {
        final FrameLayout wrapper3 = new FrameLayout(getActivity());
        wrapper3.setLayoutParams(new FrameLayout.LayoutParams(
                FrameLayout.LayoutParams.MATCH_PARENT,
                FrameLayout.LayoutParams.MATCH_PARENT));
        wrapper3.setId(3);

        final FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(
                FrameLayout.LayoutParams.MATCH_PARENT,
                FrameLayout.LayoutParams.WRAP_CONTENT);
        params.topMargin = 200;

        final TextView text = new TextView(getActivity());
        text.setLayoutParams(params);
        text.setText("fragment 3");
        wrapper3.addView(text);

        return wrapper3;
    }

    @Override
    public void onViewCreated(View view, Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);

        getChildFragmentManager().beginTransaction().addToBackStack(null)
                .add(3, new Fragment3()).commit();
    }
}

public class Fragment3 extends Fragment {
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {
        final FrameLayout wrapper4 = new FrameLayout(getActivity());
        wrapper4.setLayoutParams(new FrameLayout.LayoutParams(
                FrameLayout.LayoutParams.MATCH_PARENT,
                FrameLayout.LayoutParams.MATCH_PARENT));
        wrapper4.setId(4);

        final FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(
                FrameLayout.LayoutParams.MATCH_PARENT,
                FrameLayout.LayoutParams.WRAP_CONTENT);
        params.topMargin = 300;

        final TextView text = new TextView(getActivity());
        text.setLayoutParams(params);
        text.setText("fragment 4");
        wrapper4.addView(text);

        return wrapper4;
    }

    @Override
    public void onViewCreated(View view, Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);

        getChildFragmentManager().beginTransaction().addToBackStack(null)
                .add(4, new Fragment4()).commit();
    }
}

public class Fragment4 extends Fragment {
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {
        final FrameLayout wrapper5 = new FrameLayout(getActivity());
        wrapper5.setLayoutParams(new FrameLayout.LayoutParams(
                FrameLayout.LayoutParams.MATCH_PARENT,
                FrameLayout.LayoutParams.MATCH_PARENT));
        wrapper5.setId(5);

        final FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(
                FrameLayout.LayoutParams.MATCH_PARENT,
                FrameLayout.LayoutParams.WRAP_CONTENT);
        params.topMargin = 400;

        final TextView text = new TextView(getActivity());
        text.setLayoutParams(params);
        text.setText("fragment 5");
        wrapper5.addView(text);

        return wrapper5;
    }

    @Override
    public void onViewCreated(View view, Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
    }
}

}

exemple de code avec getFragmentManager ():

public class FragmentceptionActivity extends FragmentActivity {

@Override
protected void onCreate(Bundle arg0) {
    super.onCreate(arg0);

    final FrameLayout wrapper1 = new FrameLayout(this);
    wrapper1.setLayoutParams(new FrameLayout.LayoutParams(
            FrameLayout.LayoutParams.MATCH_PARENT,
            FrameLayout.LayoutParams.MATCH_PARENT));
    wrapper1.setId(1);

    final FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(
            FrameLayout.LayoutParams.MATCH_PARENT,
            FrameLayout.LayoutParams.WRAP_CONTENT);
    params.topMargin = 0;

    final TextView text = new TextView(this);
    text.setLayoutParams(params);
    text.setText("fragment 1");
    wrapper1.addView(text);

    setContentView(wrapper1);

    getSupportFragmentManager().beginTransaction().addToBackStack(null)
            .add(1, new Fragment1()).commit();
}

public class Fragment1 extends Fragment {
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {
        final FrameLayout wrapper2 = new FrameLayout(getActivity());
        wrapper2.setLayoutParams(new FrameLayout.LayoutParams(
                FrameLayout.LayoutParams.MATCH_PARENT,
                FrameLayout.LayoutParams.MATCH_PARENT));
        wrapper2.setId(2);

        final FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(
                FrameLayout.LayoutParams.MATCH_PARENT,
                FrameLayout.LayoutParams.WRAP_CONTENT);
        params.topMargin = 100;

        final TextView text = new TextView(getActivity());
        text.setLayoutParams(params);
        text.setText("fragment 2");
        wrapper2.addView(text);

        return wrapper2;
    }

    @Override
    public void onViewCreated(View view, Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);

        getFragmentManager().beginTransaction().addToBackStack(null)
                .add(2, new Fragment2()).commit();
    }
}

public class Fragment2 extends Fragment {
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {
        final FrameLayout wrapper3 = new FrameLayout(getActivity());
        wrapper3.setLayoutParams(new FrameLayout.LayoutParams(
                FrameLayout.LayoutParams.MATCH_PARENT,
                FrameLayout.LayoutParams.MATCH_PARENT));
        wrapper3.setId(3);

        final FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(
                FrameLayout.LayoutParams.MATCH_PARENT,
                FrameLayout.LayoutParams.WRAP_CONTENT);
        params.topMargin = 200;

        final TextView text = new TextView(getActivity());
        text.setLayoutParams(params);
        text.setText("fragment 3");
        wrapper3.addView(text);

        return wrapper3;
    }

    @Override
    public void onViewCreated(View view, Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);

        getFragmentManager().beginTransaction().addToBackStack(null)
                .add(3, new Fragment3()).commit();
    }
}

public class Fragment3 extends Fragment {
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {
        final FrameLayout wrapper4 = new FrameLayout(getActivity());
        wrapper4.setLayoutParams(new FrameLayout.LayoutParams(
                FrameLayout.LayoutParams.MATCH_PARENT,
                FrameLayout.LayoutParams.MATCH_PARENT));
        wrapper4.setId(4);

        final FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(
                FrameLayout.LayoutParams.MATCH_PARENT,
                FrameLayout.LayoutParams.WRAP_CONTENT);
        params.topMargin = 300;

        final TextView text = new TextView(getActivity());
        text.setLayoutParams(params);
        text.setText("fragment 4");
        wrapper4.addView(text);

        return wrapper4;
    }

    @Override
    public void onViewCreated(View view, Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);

        getFragmentManager().beginTransaction().addToBackStack(null)
                .add(4, new Fragment4()).commit();
    }
}

public class Fragment4 extends Fragment {
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {
        final FrameLayout wrapper5 = new FrameLayout(getActivity());
        wrapper5.setLayoutParams(new FrameLayout.LayoutParams(
                FrameLayout.LayoutParams.MATCH_PARENT,
                FrameLayout.LayoutParams.MATCH_PARENT));
        wrapper5.setId(5);

        final FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(
                FrameLayout.LayoutParams.MATCH_PARENT,
                FrameLayout.LayoutParams.WRAP_CONTENT);
        params.topMargin = 400;

        final TextView text = new TextView(getActivity());
        text.setLayoutParams(params);
        text.setText("fragment 5");
        wrapper5.addView(text);

        return wrapper5;
    }

    @Override
    public void onViewCreated(View view, Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
    }
}

}
AZ13
la source
hmmm c'est très intéressant. Je ne m'attendrais pas non plus à ce comportement. Si tel est le cas, il semble que nous ayons pu remplacer OnBackPressed et, dans cette méthode, obtenir le fragment avec des fragments enfants, puis mettre la main sur ChildFragmentManager pour exécuter une transaction pop.
Marco RS
@Marco, Exactement, mais c'est une solution de contournement. Pourquoi l'API ne fonctionne-t-elle pas correctement?
Egor

Réponses:

67

Cette solution peut être une meilleure version de @Sean answer:

@Override
public void onBackPressed() {
    // if there is a fragment and the back stack of this fragment is not empty,
    // then emulate 'onBackPressed' behaviour, because in default, it is not working
    FragmentManager fm = getSupportFragmentManager();
    for (Fragment frag : fm.getFragments()) {
        if (frag.isVisible()) {
            FragmentManager childFm = frag.getChildFragmentManager();
            if (childFm.getBackStackEntryCount() > 0) {
                childFm.popBackStack();
                return;
            }
        }
    }
    super.onBackPressed();
}

Encore une fois, j'ai préparé cette solution basée sur la réponse @Sean ci-dessus.

Comme @ AZ13 l'a dit, cette solution n'est réalisable que dans des situations de fragments enfants à un niveau. Dans le cas des fragments à plusieurs niveaux, les travaux deviennent un peu complexes, je recommande donc d'essayer cette solution uniquement dans le cas réalisable que j'ai dit. =)

Remarque: Comme getFragmentsméthode est maintenant une méthode privée, cette solution ne fonctionnera pas. Vous pouvez consulter les commentaires pour un lien qui suggère une solution à cette situation.

ismailarilik
la source
2
林奕 忠 réponse doit être utilisé à la place de celui-ci - il gère correctement plusieurs fragments imbriqués
Hameno
Est-ce vraiment du code fonctionnel? Même getFragments n'est pas une méthode publique? stackoverflow.com/a/42572412/4729203
wonsuc
Cela a fonctionné une fois mais peut ne pas fonctionner maintenant si getFragments n'est plus public.
ismailarilik
1
getFragmentsest maintenant public.
grrigore
cela ne fonctionne pas correctement si vous avez FragA -> Frag B -> Frag C à l'intérieur du téléavertisseur ...
Zhar
57

Cela ressemble à un bug. Jetez un œil à: http://code.google.com/p/android/issues/detail?id=40323

Pour une solution de contournement que j'ai utilisée avec succès (comme suggéré dans les commentaires):

    @Override
public void onBackPressed() {

    // If the fragment exists and has some back-stack entry
    if (mActivityDirectFragment != null && mActivityDirectFragment.getChildFragmentManager().getBackStackEntryCount() > 0){
        // Get the fragment fragment manager - and pop the backstack
        mActivityDirectFragment.getChildFragmentManager().popBackStack();
    }
    // Else, nothing in the direct fragment back stack
    else{
        // Let super handle the back press
        super.onBackPressed();          
    }
}
Sean
la source
12
Malheureusement, ce n'est pas une solution appropriée et juste une solution de contournement pour le cas le plus simple. Supposons simplement que vous avez un fragment, qui contient un autre fragment, qui contient un autre fragment et ainsi de suite. Ensuite, vous devez propager l'appel jusqu'au dernier fragment qui contient un ou plusieurs fragments: /
AZ13
@MuhammadBabar Non, vous ne le ferez pas. S'il s'agit d'un null, la deuxième partie de l'instruction ne sera pas évaluée car elle est déjà déterminée comme false. l'opérande est & et non | .
Sean
25

La vraie réponse à cette question se trouve dans la fonction de la transaction de fragment appelée setPrimaryNavigationFragment.

/**
 * Set a currently active fragment in this FragmentManager as the primary navigation fragment.
 *
 * <p>The primary navigation fragment's
 * {@link Fragment#getChildFragmentManager() child FragmentManager} will be called first
 * to process delegated navigation actions such as {@link FragmentManager#popBackStack()}
 * if no ID or transaction name is provided to pop to. Navigation operations outside of the
 * fragment system may choose to delegate those actions to the primary navigation fragment
 * as returned by {@link FragmentManager#getPrimaryNavigationFragment()}.</p>
 *
 * <p>The fragment provided must currently be added to the FragmentManager to be set as
 * a primary navigation fragment, or previously added as part of this transaction.</p>
 *
 * @param fragment the fragment to set as the primary navigation fragment
 * @return the same FragmentTransaction instance
 */
public abstract FragmentTransaction setPrimaryNavigationFragment(Fragment fragment);

Vous devez définir cette fonction sur le fragment parent initial lorsque l'activité l'ajoute. J'ai une fonction replaceFragment dans mon activité qui ressemble à ceci:

public void replaceFragment(int containerId, BaseFragment fragment, boolean addToBackstack) {
    FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
    fragmentTransaction.setPrimaryNavigationFragment(fragment);
    if (addToBackstack) {
        fragmentTransaction.addToBackStack(fragment.TAG);
    }

    fragmentTransaction.replace(containerId, fragment).commit();
}

Cela vous donne le même comportement que si vous reveniez du Fragment B normal au Fragment A, sauf que maintenant c'est aussi sur les fragments enfants!

Mario Bouchedid
la source
3
Super trouvaille! Cela résout le problème mentionné par l'OP d'une manière beaucoup plus propre et moins hackie.
Jona
Thats crash sauf si une partie de la bibliothèque de navigation en tant que HavHostFragment
Didi
22

Cette solution peut être une meilleure version de la réponse @ismailarilik:

Version du fragment imbriqué

private boolean onBackPressed(FragmentManager fm) {
    if (fm != null) {
        if (fm.getBackStackEntryCount() > 0) {
            fm.popBackStack();
            return true;
        }

        List<Fragment> fragList = fm.getFragments();
        if (fragList != null && fragList.size() > 0) {
            for (Fragment frag : fragList) {
                if (frag == null) {
                    continue;
                }
                if (frag.isVisible()) {
                    if (onBackPressed(frag.getChildFragmentManager())) {
                        return true;
                    }
                }
            }
        }
    }
    return false;
}

@Override
public void onBackPressed() {
    FragmentManager fm = getSupportFragmentManager();
    if (onBackPressed(fm)) {
        return;
    }
    super.onBackPressed();
}
林奕 忠
la source
cela ne semble pas fonctionner lorsqu'il y a plusieurs niveaux de fragments imbriqués
splinter123
Cloud tu me donnes ton architecture de fragments imbriqués? Dans mon application, cela fonctionne. Peut-être que quelque chose me manque.
林奕 忠
9
L'ordre pour faire apparaître la backstack n'est pas comme je m'y attendais dans cet exemple. Je pense que vous devriez d'abord trouver le fragment visible le plus profondément imbriqué dans l'arborescence et essayer de faire apparaître la backstack dessus, puis remonter progressivement l'arborescence à la recherche d'autres fragments visibles où vous pourriez faire apparaître la backstack jusqu'à ce que vous en obteniez un. Une solution rapide qui gère les cas les plus simples consiste à modifier la fonction onBackPressed (FragmentManager fm) et à changer le bloc qui fait apparaître la pile arrière avec celui qui énumère les fragments. De cette façon, faire apparaître la backstack dans des fragments d'enfants plus profondément imbriqués se produira en premier
Theo
1
De plus, lorsque vous avez beaucoup de fragments imbriqués, chacun avec ses fragments enfants pouvant couvrir une partie de l'écran, l'idée d'avoir une backstack distincte pour chaque gestionnaire de fragments enfants cesse de faire sens. La backstack doit être unifiée sur tous les fragments de l'activité et suivre les changements de fragment dans l'ordre dans lequel ils se produisent, quel que soit le niveau d'imbrication du fragment.
Theo
Est-il possible d'utiliser cette solution sans le SupportFragmentManageret à la place uniquement avec le Standard FragmentManager?
Peter Chappy
13

Avec cette réponse, il gérera la vérification arrière récursive et donnera à chaque fragment la possibilité de remplacer le comportement par défaut. Cela signifie que vous pouvez demander à un fragment qui héberge un ViewPager de faire quelque chose de spécial, comme faire défiler jusqu'à la page qui en tant que back-stack, ou faire défiler jusqu'à la page d'accueil, puis au retour suivant, appuyez sur Quitter.

Ajoutez ceci à votre activité qui étend AppCompatActivity.

@Override
public void onBackPressed()
{
    if(!BaseFragment.handleBackPressed(getSupportFragmentManager())){
        super.onBackPressed();
    }
}

Ajoutez ceci à votre BaseFragment ou à la classe dont vous pouvez hériter de tous vos fragments.

public static boolean handleBackPressed(FragmentManager fm)
{
    if(fm.getFragments() != null){
        for(Fragment frag : fm.getFragments()){
            if(frag != null && frag.isVisible() && frag instanceof BaseFragment){
                if(((BaseFragment)frag).onBackPressed()){
                    return true;
                }
            }
        }
    }
    return false;
}

protected boolean onBackPressed()
{
    FragmentManager fm = getChildFragmentManager();
    if(handleBackPressed(fm)){
        return true;
    }
    else if(getUserVisibleHint() && fm.getBackStackEntryCount() > 0){
        fm.popBackStack();
        return true;
    }
    return false;
}
Simon
la source
c'est une excellente réponse, travaillant au niveau d'API 23/24 et avec le support lib 24.1.1
Rinav
J'utilise cela depuis un certain temps maintenant, c'est une excellente solution - mais récemment avoir compilé des avertissements sur l'accès direct à getFragments () depuis un groupe de bibliothèques extérieur. Avez-vous déjà mis à jour avec une solution de contournement?
Simon Huckett
est-ce que quelque chose est résolu ou en 2020, nous avons toujours le problème et devrions implémenter votre solution ?? !!
Zhar
3

Ce code naviguera dans l'arborescence des gestionnaires de fragments et retournera le dernier qui a été ajouté qui contient tous les fragments qu'il peut sortir de la pile:

private FragmentManager getLastFragmentManagerWithBack(FragmentManager fm)
{
  FragmentManager fmLast = fm;

  List<Fragment> fragments = fm.getFragments();

  for (Fragment f : fragments)
  {
    if ((f.getChildFragmentManager() != null) && (f.getChildFragmentManager().getBackStackEntryCount() > 0))
    {
      fmLast = f.getFragmentManager();
      FragmentManager fmChild = getLastFragmentManagerWithBack(f.getChildFragmentManager());

      if (fmChild != fmLast)
        fmLast = fmChild;
    }
  }

  return fmLast;
}

Appelez la méthode:

@Override
public void onBackPressed()
{
  FragmentManager fm = getLastFragmentManagerWithBack(getSupportFragmentManager());

  if (fm.getBackStackEntryCount() > 0)
  {
    fm.popBackStack();
    return;
  }

  super.onBackPressed();
}
AndroidDev
la source
Cela fonctionne pour tous les fragments imbriqués s'ils sont correctement enregistrés !! +100
Pratik Saluja
2

La raison en est que votre activité dérive de FragmentActivity, qui gère la pression de la touche RETOUR (voir la ligne 173 de FragmentActivity .

Dans notre application, j'utilise un ViewPager (avec des fragments) et chaque fragment peut avoir des fragments imbriqués. La façon dont j'ai géré cela est de:

  • définition d'une interface OnBackKeyPressedListener avec une seule méthode void onBackKeyPressed()
  • implémenté cette interface dans les fragments "top" que ViewPager montre
  • écraser onKeyDown et détecter BACK press, et appeler onBackKeyPressed dans un fragment actuellement actif dans le pager de vue.

Notez également que j'utilise getChildFragmentManager()dans les fragments pour imbriquer correctement les fragments. Vous pouvez voir une discussion et une explication dans cet article des développeurs Android .

miha
la source
2

si vous utilisez un fragment dans un fragment, utilisez getChildFragmentManager()

getChildFragmentManager().beginTransaction().replace(R.id.fragment_name, fragment).addToBackStack(null).commit();

si vous utilisez un fragment enfant et le remplacez, utilisez getParentFragmentManager()

getParentFragmentManager().beginTransaction().replace(R.id.fragment_name, fragment).addToBackStack(null).commit();

si les deux ne fonctionnent pas pour vous, essayez ceci getActivity().getSupportFragmentManager()

getActivity().getSupportFragmentManager().beginTransaction().replace(R.id.fragment_name, fragment).addToBackStack(null).commit();
Chetan
la source
1

J'ai pu gérer la pile arrière de fragment en ajoutant au fragment parent cette méthode à la méthode onCreate View () et en passant la vue racine.

private void catchBackEvent(View v){
    v.setFocusableInTouchMode(true);
    v.requestFocus();
    v.setOnKeyListener( new OnKeyListener()
    {
        @Override
        public boolean onKey( View v, int keyCode, KeyEvent event )
        {
            if( keyCode == KeyEvent.KEYCODE_BACK )
            {
                if(isEnableFragmentBackStack()){
                    getChildFragmentManager().popBackStack();
                                    setEnableFragmentBackStack(false);
                    return true;
                }
                else
                    return false;   
            }
            return false;
        }
    } );
}

La méthode isEnableFragmentBackStack () est un drapeau booléen pour savoir quand je suis sur le fragment principal ou sur le suivant.

Assurez-vous que lorsque vous validez le fragment qui doit avoir une pile, vous devez ajouter la méthode addToBackstack.

EkKoZ
la source
1

Cette solution peut être meilleure, car elle vérifie tous les niveaux de fragments imbriqués:

 /**
 * This method will go check all the back stacks of the added fragments and their nested fragments
 * to the the {@code FragmentManager} passed as parameter.
 * If there is a fragment and the back stack of this fragment is not empty,
 * then emulate 'onBackPressed' behaviour, because in default, it is not working.
 *
 * @param fm the fragment manager to which we will try to dispatch the back pressed event.
 * @return {@code true} if the onBackPressed event was consumed by a child fragment, otherwise {@code false}.
 */
public static boolean dispatchOnBackPressedToFragments(FragmentManager fm) {

    List<Fragment> fragments = fm.getFragments();
    boolean result;
    if (fragments != null && !fragments.isEmpty()) {
        for (Fragment frag : fragments) {
            if (frag != null && frag.isAdded() && frag.getChildFragmentManager() != null) {
                // go to the next level of child fragments.
                result = dispatchOnBackPressedToFragments(frag.getChildFragmentManager());
                if (result) return true;
            }
        }
    }

    // if the back stack is not empty then we pop the last transaction.
    if (fm.getBackStackEntryCount() > 0) {
        fm.popBackStack();
        fm.executePendingTransactions();
        return true;
    }

    return false;
}

dans votre activité, onBackPressedvous pouvez simplement l'appeler ainsi:

FragmentManager fm = getSupportFragmentManager();
                // if there is a fragment and the back stack of this fragment is not empty,
                // then emulate 'onBackPressed' behaviour, because in default, it is not working
                if (!dispatchOnBackPressedToFragments(fm)) {
                    // if no child fragment consumed the onBackPressed event,
                    // we execute the default behaviour.
                    super.onBackPressed();
                }
ahmed_khan_89
la source
1

Merci à tous pour leur aide, cette (version peaufinée) fonctionne pour moi:

@Override
public void onBackPressed() {
    if (!recursivePopBackStack(getSupportFragmentManager())) {
        super.onBackPressed();
    }
}

/**
 * Recursively look through nested fragments for a backstack entry to pop
 * @return: true if a pop was performed
 */
public static boolean recursivePopBackStack(FragmentManager fragmentManager) {
    if (fragmentManager.getFragments() != null) {
        for (Fragment fragment : fragmentManager.getFragments()) {
            if (fragment != null && fragment.isVisible()) {
                boolean popped = recursivePopBackStack(fragment.getChildFragmentManager());
                if (popped) {
                    return true;
                }
            }
        }
    }

    if (fragmentManager.getBackStackEntryCount() > 0) {
        fragmentManager.popBackStack();
        return true;
    }

    return false;
}

REMARQUE: vous souhaiterez probablement également définir la couleur d'arrière-plan de ces fragments imbriqués sur la couleur d'arrière-plan de la fenêtre du thème de l'application, car par défaut, ils sont transparents. Un peu en dehors de la portée de cette question, mais cela est accompli en résolvant l'attribut android.R.attr.windowBackground et en définissant l'arrière-plan de la vue Fragment sur cet ID de ressource.

Steven L
la source
1

Plus de 5 ans et cette question est toujours d'actualité. Si vous ne souhaitez pas utiliser fragmentManager.getFragments () en raison de sa restriction. Étendez et utilisez les classes ci-dessous:

NestedFragmentActivity.java

abstract public class NestedFragmentActivity extends AppCompatActivity {

    private final Stack<Integer> mActiveFragmentIdStack = new Stack<>();
    private final Stack<String> mActiveFragmentTagStack = new Stack<>();

    @Override
    public void onBackPressed() {
        if (mActiveFragmentIdStack.size() > 0 && mActiveFragmentTagStack.size() > 0) {

            // Find by id
            int lastFragmentId = mActiveFragmentIdStack.lastElement();
            NestedFragment nestedFragment = (NestedFragment) getSupportFragmentManager().findFragmentById(lastFragmentId);

            // If cannot find by id, find by tag
            if (nestedFragment == null) {
                String lastFragmentTag = mActiveFragmentTagStack.lastElement();
                nestedFragment = (NestedFragment) getSupportFragmentManager().findFragmentByTag(lastFragmentTag);
            }

            if (nestedFragment != null) {
                nestedFragment.onBackPressed();
            }

            // If cannot find by tag, then simply pop
            mActiveFragmentTagStack.pop();
            mActiveFragmentIdStack.pop();

        } else {
            super.onBackPressed();
        }
    }

    public void addToBackStack(int fragmentId, String fragmentTag) {
        mActiveFragmentIdStack.add(fragmentId);
        mActiveFragmentTagStack.add(fragmentTag);
    }
}

NestedFragment.java

abstract public class NestedFragment extends Fragment {

    private final Stack<Integer> mActiveFragmentIdStack = new Stack<>();
    private final Stack<String> mActiveFragmentTagStack = new Stack<>();

    private NestedFragmentActivity mParentActivity;
    private NestedFragment mParentFragment;

    @Override
    public void onAttach(Context context) {
        super.onAttach(context);

        if (getParentFragment() == null) {
            try {
                mParentActivity = (NestedFragmentActivity) context;
            } catch (ClassCastException e) {
                throw new ClassCastException(context.toString()
                        + " must implement " + NestedFragmentActivity.class.getName());
            }
        } else {
            try {
                mParentFragment = (NestedFragment) getParentFragment();
            } catch (ClassCastException e) {
                throw new ClassCastException(getParentFragment().getClass().toString()
                        + " must implement " + NestedFragment.class.getName());
            }
        }
    }

    public void onBackPressed() {

        if (mActiveFragmentIdStack.size() > 0 && mActiveFragmentTagStack.size() > 0) {

            // Find by id
            int lastFragmentId = mActiveFragmentIdStack.lastElement();
            NestedFragment nestedFragment = (NestedFragment) getChildFragmentManager().findFragmentById(lastFragmentId);

            // If cannot find by id, find by tag
            if (nestedFragment == null) {
                String lastFragmentTag = mActiveFragmentTagStack.lastElement();
                nestedFragment = (NestedFragment) getChildFragmentManager().findFragmentByTag(lastFragmentTag);
            }

            if (nestedFragment != null) {
                nestedFragment.onBackPressed();
            }

            // If cannot find by tag, then simply pop
            mActiveFragmentIdStack.pop();
            mActiveFragmentTagStack.pop();

        } else {
            getChildFragmentManager().popBackStack();
        }
    }

    private void addToBackStack(int fragmentId, String fragmentTag) {
        mActiveFragmentIdStack.add(fragmentId);
        mActiveFragmentTagStack.add(fragmentTag);
    }

    public void addToParentBackStack() {
        if (mParentFragment != null) {
            mParentFragment.addToBackStack(getId(), getTag());
        } else if (mParentActivity != null) {
            mParentActivity.addToBackStack(getId(), getTag());
        }
    }
}

Explication:

Chaque activité et fragment issu des classes ci-dessus gère leur propre back stack pour chacun de leurs enfants, et les enfants des enfants, etc. La backstack est simplement un enregistrement de balises / identifiants de "fragment actif". La mise en garde est donc de toujours fournir une balise et / ou un identifiant pour votre fragment.

Lors de l'ajout à la backstack dans un childFragmentManager, vous devrez également appeler "addToParentBackStack ()". Cela garantit que le tag / id du fragment est ajouté dans le fragment / activité parent pour les pops ultérieurs.

Exemple:

    getChildFragmentManager().beginTransaction().replace(
            R.id.fragment,
            fragment,
            fragment.getTag()
    ).addToBackStack(null).commit();
    addToParentBackStack();
chasseur
la source
1

Je l'ai implémenté correctement si personne n'a trouvé de réponse jusqu'à présent

ajoutez simplement cette méthode sur votre fragment imbriqué enfant

   @Override
public void onCreate(@Nullable Bundle savedInstanceState) {
    // This callback will only be called when MyFragment is at least Started.
    OnBackPressedCallback callback = new OnBackPressedCallback(true ) {
        @Override
        public void handleOnBackPressed() {
            // Handle the back button event
            FragmentManager fm= getFragmentManager();
            if (fm != null) {
                if (fm.getBackStackEntryCount() > 0) {
                    fm.popBackStack();
                    Log.e( "Frag","back" );

                }

                List<Fragment> fragList = fm.getFragments();
                if (fragList != null && fragList.size() > 0) {
                    for (Fragment frag : fragList) {
                        if (frag == null) {
                            continue;
                        }
                        if (frag.isVisible()) {
                          Log.e( "Frag","Visible" );
                        }
                    }
                }
            }


        }
    };
    requireActivity().getOnBackPressedDispatcher().addCallback(this, callback);
    super.onCreate( savedInstanceState );
}
Sunil Chaudhary
la source
0

J'ai résolu ce problème en conservant le fragment actuellement ouvert dans la propriété de l'activité. Ensuite, je remplace la méthode

 // INSIDE ACTIVITY
 override fun onBackPressed() {
    val fragment = currentFragment

    if(fragment != null && fragment.childFragmentManager.fragments.any()){
        fragment.childFragmentManager.popBackStack()
    }
    else {
        super.onBackPressed()
    }
}

C'est ainsi que j'ajoute un fragment enfant dans un fragment actuellement ouvert à partir de lui-même.

 // INSIDE FRAGMENT
 menu_fragment_view.setBackgroundColor(-((Math.random() * 10000 ).toInt() % 30000)) // to see change
 add_fragment_button.setOnClickListener {
        val transaction = childFragmentManager.beginTransaction()

        transaction.add(R.id.menu_fragment_fragment_holder, MenuFragment())

        transaction.addToBackStack(null)

        transaction.commit()
    }

Il s'agit de la mise en page xml du fragment parent et du fragment ajouté

 <?xml version="1.0" encoding="utf-8"?>
 <androidx.constraintlayout.widget.ConstraintLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:app="http://schemas.android.com/apk/res-auto"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
     android:id="@+id/menu_fragment_view">
     <Button
         app:layout_constraintTop_toTopOf="parent"
         app:layout_constraintLeft_toLeftOf="parent"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:id="@+id/add_fragment_button"
         android:text="Just add fragment"/>
     <FrameLayout
         android:id="@+id/menu_fragment_fragment_holder"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
         app:layout_constraintTop_toTopOf="parent"
         app:layout_constraintLeft_toLeftOf="parent"/> 
</androidx.constraintlayout.widget.ConstraintLayout>
Michal Zhradnk Nono3551
la source
0

Après avoir observé certaines solutions présentées ici, j'ai trouvé que pour permettre la flexibilité et le contrôle du fragment parent quant au moment où la pile de sauts ou lorsque l'action de retour doit être ignorée, j'utilise plutôt une telle implémentation:

Définition d'une interface "ParentFragment":

interface ParentFragment {
/**
 * Fragments that host child fragments and want to control their BackStack behaviour when the back button is pressed should implement this
 *
 * @return true if back press was handled, false otherwise
 */
fun onBackPressed(): Boolean

}

Remplacer "onBackPressed" dans l'activité parent (ou dans BaseActivity):

override fun onBackPressed() {
    val fm: FragmentManager = supportFragmentManager
    for (frag in fm.fragments) {
        if (frag.isVisible && frag is ParentFragment && frag.onBackPressed()) {
            return
        }
    }
    super.onBackPressed()
}

Et puis autorisez le fragment parent à gérer comme il le souhaite, par exemple:

override fun onBackPressed(): Boolean {
    val childFragment = childFragmentManager.findFragmentByTag(SomeChildFragment::class.java.simpleName)
    if (childFragment != null && childFragment.isVisible) {
        // Only for that case, pop the BackStack (perhaps when other child fragments are visible don't)
        childFragmentManager.popBackStack()
        return true
    }
    return false
}

Cela permet d'éviter de penser qu'il y a un fragment enfant légitime à supprimer lors de l'utilisation d'un pager de vue par exemple (et le nombre d'entrées de back stack> 0).

Ai-je
la source
-1

Si vous avez un DialogFragmentqui à son tour a des fragments imbriqués, la «solution de contournement» est un peu différente. Au lieu de définir un onKeyListenersur le rootView, vous devrez le faire avec le Dialog. Vous allez également mettre en place un DialogInterface.OnKeyListeneret non Viewun. Bien sûr, souvenez-vous addToBackStack!

Btw, avoir 1 fragment sur la backstack pour déléguer le rappel à l'activité est mon cas d'utilisation personnel. Les scénarios typiques pourraient être que le nombre soit égal à 0.

Voici ce que vous devez faire dans le onCreateDialog

    @Override
    public Dialog onCreateDialog(Bundle savedInstanceState) {
        Dialog dialog =  super.onCreateDialog(savedInstanceState);
        dialog.setOnKeyListener(new DialogInterface.OnKeyListener() {
            @Override
            public boolean onKey(DialogInterface dialog, int keyCode, KeyEvent event) {
                if(keyCode == KeyEvent.KEYCODE_BACK){
                    FragmentManager cfm = getChildFragmentManager();
                    if(cfm.getBackStackEntryCount()>1){
                        cfm.popBackStack();
                        return true;
                    }   
                }   
                return false;
            }
        });
        return dialog;
    }
Sachin Ahuja
la source
Pas vraiment compréhensible ou incomplet. DialogFragment ou son fragment de superclasse n'ont pas setOnKeyListener.
Ixx
@Ixx Vous devez activer Dialog(non DialogFragment) l'écouteur et l'écouteur que vous utilisez est DialogInterface.OnKeyListener- le code fonctionne et est complet (et en cours d'utilisation). Pourquoi ne donnez-vous pas votre situation et peut-être que je peux vous aider.
Sachin Ahuja
-1

pour ChildFragments cela fonctionne.

@Override
    public void onBackPressed() {

 if (getSupportFragmentManager().getBackStackEntryCount() > 0) {
            getSupportFragmentManager().popBackStack();
        } else {
            doExit(); //super.onBackPressed();
        }
}
user3854496
la source