Fragments onResume de la pile arrière

94

J'utilise le package de compatibilité pour utiliser Fragments avec Android 2.2. Lorsque vous utilisez des fragments et que vous ajoutez des transitions entre eux à la backstack, j'aimerais obtenir le même comportement de onResume d'une activité, c'est-à-dire chaque fois qu'un fragment est amené au "premier plan" (visible par l'utilisateur) après sa sortie du backstack, j'aimerais qu'une sorte de rappel soit activée dans le fragment (pour effectuer certaines modifications sur une ressource d'interface utilisateur partagée, par exemple).

J'ai vu qu'il n'y a pas de rappel intégré dans le cadre du fragment. y a-t-il une bonne pratique pour y parvenir?

oriharel
la source
13
Excellente question. J'essaie de changer le titre de la barre d'action en fonction du fragment visible comme cas d'utilisation pour cette scénarion et cela me semble être un rappel manquant dans l'API.
Manfred Moser
3
Votre activité peut définir le titre étant donné qu'elle sait quels fragments sont affichés à tout moment. Aussi, je suppose que vous pourriez faire quelque chose onCreateViewqui sera appelé pour le fragment après un pop.
PJL
1
@PJL +1 Selon la référence, c'est ainsi que cela doit être fait. Cependant, je préfère la manière de l'auditeur
momo

Réponses:

115

Faute d'une meilleure solution, j'ai fait fonctionner cela pour moi: supposons que j'ai 1 activité (MyActivity) et quelques fragments qui se remplacent (un seul est visible à la fois).

Dans MyActivity, ajoutez cet écouteur:

getSupportFragmentManager().addOnBackStackChangedListener(getListener());

(Comme vous pouvez le voir, j'utilise le package de compatibilité).

Implémentation de getListener:

private OnBackStackChangedListener getListener()
    {
        OnBackStackChangedListener result = new OnBackStackChangedListener()
        {
            public void onBackStackChanged() 
            {                   
                FragmentManager manager = getSupportFragmentManager();

                if (manager != null)
                {
                    MyFragment currFrag = (MyFragment) manager.findFragmentById(R.id.fragmentItem);

                    currFrag.onFragmentResume();
                }                   
            }
        };

        return result;
    }

MyFragment.onFragmentResume()sera appelé après avoir appuyé sur "Retour". quelques mises en garde cependant:

  1. Cela suppose que vous avez ajouté toutes les transactions à la backstack (en utilisant FragmentTransaction.addToBackStack())
  2. Il sera activé à chaque changement de pile (vous pouvez stocker d'autres éléments dans la pile arrière comme une animation) afin que vous puissiez recevoir plusieurs appels pour la même instance de fragment.
oriharel
la source
3
Vous devez accepter votre propre réponse comme correcte si cela a fonctionné pour vous.
powerj1984
7
Comment cela fonctionne-t-il en termes d'ID de fragment sur lequel vous interrogez? En quoi est-ce différent pour chaque fragment et le bon se trouve dans la pile arrière?
Manfred Moser
2
@Warpzit cette méthode n'est pas appelée, et les documents Android admettent tranquillement qu'elle ne sera jamais appelée (elle ne sera appelée que lorsque l'activité est reprise - ce qui n'est jamais, si cette activité est déjà active)
Adam
2
C'est ridicule que nous devions faire ça! Mais heureux que quelqu'un d'autre ait pu trouver cette "fonctionnalité"
stevebot
3
onResume () n'est PAS appelé
Marty Miller
33

J'ai un peu changé la solution suggérée. Fonctionne mieux pour moi comme ça:

private OnBackStackChangedListener getListener() {
    OnBackStackChangedListener result = new OnBackStackChangedListener() {
        public void onBackStackChanged() {
            FragmentManager manager = getSupportFragmentManager();
            if (manager != null) {
                int backStackEntryCount = manager.getBackStackEntryCount();
                if (backStackEntryCount == 0) {
                    finish();
                }
                Fragment fragment = manager.getFragments()
                                           .get(backStackEntryCount - 1);
                fragment.onResume();
            }
        }
    };
    return result;
}
Brotoo25
la source
1
Veuillez expliquer la différence et pourquoi l'utiliser.
Math chiller
2
La différence entre ma solution et la précédente est qu'à chaque changement de fragment comme l'ajout, le remplacement ou le recul dans la pile de fragments, la méthode "onResume" du fragment supérieur sera appelée. Il n'y a pas d'identifiant de fragment codé en dur dans cette méthode. Fondamentalement, il appelle onResume () dans le fragment que vous regardez à chaque changement, peu importe s'il était déjà chargé dans la mémoire ou non.
Brotoo25
9
Il n'y a pas d'enregistrement pour une méthode appelée getFragments()dans SupportFragmentManager ... -1: - /
Protostome
1
OnResume () est appelé.Mais j'obtiens un écran vide..Un autre moyen de résoudre ce problème?
kavie
Je pense que cela conduit à une exception ArrayOutOfBounds si backStackEntryCount est égal à 0. Est-ce que je me trompe? J'ai essayé de modifier le message, mais il a été rejeté.
Mike T
6

Après un, popStackBack()vous pouvez utiliser le rappel suivant: onHiddenChanged(boolean hidden)dans votre fragment

user2230304
la source
2
Cela ne semble pas fonctionner avec les fragments de compatibilité d'application.
Lo-Tan
C'est ce que j'ai utilisé. Merci!
Albert Vila Calvo le
La solution la plus simple! Ajoutez simplement une vérification si le fragment est actuellement visible et le tour est joué, vous pouvez définir le titre de la barre d'applications.
EricH206
4

La section suivante d'Android Developers décrit un mécanisme de communication Création de rappels d'événement vers l'activité . Pour en citer une phrase:

Un bon moyen de le faire est de définir une interface de rappel à l'intérieur du fragment et d'exiger que l'activité hôte l'implémente. Lorsque l'activité reçoit un rappel via l'interface, elle peut partager les informations avec d'autres fragments de la mise en page si nécessaire.

Edit: Le fragment a un onStart(...)qui est appelé lorsque le fragment est visible par l'utilisateur. De même, une onResume(...)fois visible et en cours d'exécution. Ceux-ci sont liés à leurs homologues d'activité. En bref: utiliseronResume()

PJL
la source
1
Je recherche juste une méthode dans la classe Fragment qui est activée chaque fois qu'elle est montrée à l'utilisateur. que ce soit à cause d'un FragmentTransaction.add () / replace () ou d'un FragmentManager.popBackStack ().
oriharel
@oriharel Voir la réponse mise à jour. Lorsque l'activité est chargée, vous recevrez divers appels, onAttach, onCreate, onActivityCreated, onStart, onResume. Si vous avez appelé `setRetainInstance (true) ', vous n'obtiendrez pas un onCreate lorsque le fragment sera réafficher en cliquant à nouveau.
PJL
15
onStart () n'est jamais appelé en cliquant sur "Retour" entre des fragments dans la même activité. J'ai essayé la plupart des rappels de la documentation et aucun d'entre eux n'est appelé dans un tel scénario. voir ma réponse pour une solution de contournement.
oriharel
hmm avec compat lib v4 Je vois un onResume pour mon fragment. J'aime la réponse de @ oriharel, car elle fait la distinction entre devenir visible à travers un popBackStack plutôt que simplement être mis au premier plan.
PJL
3

Dans mon activité onCreate ()

getSupportFragmentManager().addOnBackStackChangedListener(getListener());

Utilisez cette méthode pour attraper un fragment spécifique et appeler onResume ()

private FragmentManager.OnBackStackChangedListener getListener()
    {
        FragmentManager.OnBackStackChangedListener result = new FragmentManager.OnBackStackChangedListener()
        {
            public void onBackStackChanged()
            {
                Fragment currentFragment = getSupportFragmentManager().findFragmentById(R.id.fragment_container);
                if (currentFragment instanceof YOURFRAGMENT) {
                    currentFragment.onResume();
                }
            }
        };

        return result;
    }
Quan Nguyen
la source
2

Un peu amélioré et intégré dans une solution de gestionnaire.

Choses à garder à l'esprit. FragmentManager n'est pas un singleton, il ne gère que des fragments dans Activity, donc dans chaque activité, il sera nouveau. De plus, cette solution jusqu'à présent ne prend pas en compte ViewPager qui appelle la méthode setUserVisibleHint () aidant à contrôler la visibilité des fragments.

N'hésitez pas à utiliser les classes suivantes pour résoudre ce problème (utilise l'injection Dagger2). Appel en activité:

//inject FragmentBackstackStateManager instance to myFragmentBackstackStateManager
FragmentManager fragmentManager = getSupportFragmentManager(); 
myFragmentBackstackStateManager.apply(fragmentManager);

FragmentBackstackStateManager.java:

@Singleton
public class FragmentBackstackStateManager {

    private FragmentManager fragmentManager;

    @Inject
    public FragmentBackstackStateManager() {
    }

    private BackstackCallback backstackCallbackImpl = new BackstackCallback() {
        @Override
        public void onFragmentPushed(Fragment parentFragment) {
            parentFragment.onPause();
        }

        @Override
        public void onFragmentPopped(Fragment parentFragment) {
            parentFragment.onResume();
        }
    };

    public FragmentBackstackChangeListenerImpl getListener() {
        return new FragmentBackstackChangeListenerImpl(fragmentManager, backstackCallbackImpl);
    }

    public void apply(FragmentManager fragmentManager) {
        this.fragmentManager = fragmentManager;
        fragmentManager.addOnBackStackChangedListener(getListener());
    }
}

FragmentBackstackChangeListenerImpl.java:

public class FragmentBackstackChangeListenerImpl implements FragmentManager.OnBackStackChangedListener {

    private int lastBackStackEntryCount = 0;
    private final FragmentManager fragmentManager;
    private final BackstackCallback backstackChangeListener;

    public FragmentBackstackChangeListenerImpl(FragmentManager fragmentManager, BackstackCallback backstackChangeListener) {
        this.fragmentManager = fragmentManager;
        this.backstackChangeListener = backstackChangeListener;
        lastBackStackEntryCount = fragmentManager.getBackStackEntryCount();
    }

    private boolean wasPushed(int backStackEntryCount) {
        return lastBackStackEntryCount < backStackEntryCount;
    }

    private boolean wasPopped(int backStackEntryCount) {
        return lastBackStackEntryCount > backStackEntryCount;
    }

    private boolean haveFragments() {
        List<Fragment> fragmentList = fragmentManager.getFragments();
        return fragmentList != null && !fragmentList.isEmpty();
    }


    /**
     * If we push a fragment to backstack then parent would be the one before => size - 2
     * If we pop a fragment from backstack logically it should be the last fragment in the list, but in Android popping a fragment just makes list entry null keeping list size intact, thus it's also size - 2
     *
     * @return fragment that is parent to the one that is pushed to or popped from back stack
     */
    private Fragment getParentFragment() {
        List<Fragment> fragmentList = fragmentManager.getFragments();
        return fragmentList.get(Math.max(0, fragmentList.size() - 2));
    }

    @Override
    public void onBackStackChanged() {
        int currentBackStackEntryCount = fragmentManager.getBackStackEntryCount();
        if (haveFragments()) {
            Fragment parentFragment = getParentFragment();

            //will be null if was just popped and was last in the stack
            if (parentFragment != null) {
                if (wasPushed(currentBackStackEntryCount)) {
                    backstackChangeListener.onFragmentPushed(parentFragment);
                } else if (wasPopped(currentBackStackEntryCount)) {
                    backstackChangeListener.onFragmentPopped(parentFragment);
                }
            }
        }

        lastBackStackEntryCount = currentBackStackEntryCount;
    }
}

BackstackCallback.java:

public interface BackstackCallback {
    void onFragmentPushed(Fragment parentFragment);

    void onFragmentPopped(Fragment parentFragment);
}
AAverin
la source
1

Si un fragment est mis en backstack, Android détruit simplement sa vue. L'instance de fragment elle-même n'est pas tuée. Une façon simple de commencer devrait être d'écouter l'événement onViewCreated, et d'y mettre la logique «onResume ()».

boolean fragmentAlreadyLoaded = false;
    @Override
    public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
        // TODO Auto-generated method stub
        super.onViewCreated(view, savedInstanceState);

        if (savedInstanceState == null && !fragmentAlreadyLoaded) {
            fragmentAlreadyLoaded = true;

            // Code placed here will be executed once
        }

        //Code placed here will be executed even when the fragment comes from backstack
    }
Franjo
la source
0

C'est la bonne réponse que vous pouvez appeler onResume () à condition que le fragment soit attaché à l'activité. Vous pouvez également utiliser onAttach et onDetach

iamlukeyb
la source
1
Pouvez-vous poster un exemple s'il vous plaît?
kavie
0

onResume () pour le fragment fonctionne très bien ...

public class listBook extends Fragment {

    private String listbook_last_subtitle;
...

    @Override
       public void onCreate(Bundle savedInstanceState) {

        String thisFragSubtitle = (String) getActivity().getActionBar().getSubtitle();
        listbook_last_subtitle = thisFragSubtitle;
       }
...

    @Override
        public void onResume(){
            super.onResume();
            getActivity().getActionBar().setSubtitle(listbook_last_subtitle);
        }
...
Roshan Poudyal
la source
0
public abstract class RootFragment extends Fragment implements OnBackPressListener {

 @Override
 public boolean onBackPressed() {
  return new BackPressImpl(this).onBackPressed();
 }

 public abstract void OnRefreshUI();

}


public class BackPressImpl implements OnBackPressListener {

 private Fragment parentFragment;

 public BackPressImpl(Fragment parentFragment) {
  this.parentFragment = parentFragment;
 }

 @Override
 public boolean onBackPressed() {
  ((RootFragment) parentFragment).OnRefreshUI();
 }
}

et dernière mesure votre Frament de RootFragment pour voir l'effet

Luu Quoc Thang
la source
0

Ma solution de contournement consiste à obtenir le titre actuel de la barre d'actions dans le fragment avant de le définir sur le nouveau titre. De cette façon, une fois que le fragment est sorti, je peux revenir à ce titre.

@Override
public void onResume() {
    super.onResume();
    // Get/Backup current title
    mTitle = ((ActionBarActivity) getActivity()).getSupportActionBar()
            .getTitle();
    // Set new title
    ((ActionBarActivity) getActivity()).getSupportActionBar()
        .setTitle(R.string.this_fragment_title);
}

@Override
public void onDestroy() {
    // Set title back
    ((ActionBarActivity) getActivity()).getSupportActionBar()
        .setTitle(mTitle);

    super.onDestroy();
}
Bonbons Mahendran
la source
0

J'ai utilisé enum FragmentTagspour définir toutes mes classes de fragments.

TAG_FOR_FRAGMENT_A(A.class),
TAG_FOR_FRAGMENT_B(B.class),
TAG_FOR_FRAGMENT_C(C.class)

passer FragmentTags.TAG_FOR_FRAGMENT_A.name()comme étiquette de fragment.

et maintenant

@Override
public void onBackPressed(){
   FragmentManager fragmentManager = getFragmentManager();
   Fragment current
   = fragmentManager.findFragmentById(R.id.fragment_container);
    FragmentTags fragmentTag = FragmentTags.valueOf(current.getTag());

  switch(fragmentTag){
    case TAG_FOR_FRAGMENT_A:
        finish();
        break;
   case TAG_FOR_FRAGMENT_B:
        fragmentManager.popBackStack();
        break;
   case default: 
   break;
}
Ali
la source