Comment déterminer quand Fragment devient visible dans ViewPager

Problème: Le fragment onResume() dans ViewPager est déclenché avant que le fragment ne soit réellement visible.

Par exemple, j’ai 2 fragments avec ViewPager et FragmentPagerAdapter . Le deuxième fragment est uniquement disponible pour les utilisateurs autorisés et je dois demander à l’utilisateur de se connecter lorsque le fragment devient visible (à l’aide d’une boîte de dialog d’alerte).

MAIS le ViewPager crée le deuxième fragment lorsque le premier est visible afin de mettre en cache le second fragment et le rend visible lorsque l’utilisateur commence à le glisser.

Donc, l’ onResume() est déclenché dans le second fragment bien avant qu’il ne devienne visible. C’est pourquoi j’essaie de trouver un événement qui se déclenche lorsque le second fragment devient visible pour afficher un dialog au moment approprié.

Comment cela peut-il être fait?

MISE À JOUR : Android Support Library (rev 11) a finalement corrigé le problème d’ getUserVisibleHint() l’utilisateur , maintenant, si vous utilisez la bibliothèque de support pour les fragments, vous pouvez utiliser getUserVisibleHint() ou redéfinir setUserVisibleHint() pour capturer les modifications.

UPDATE 1 Voici un petit problème avec getUserVisibleHint() . Cette valeur est true par défaut.

 // Hint provided by the app that this fragment is currently visible to the user. boolean mUserVisibleHint = true; 

Donc, il peut y avoir un problème lorsque vous essayez de l’utiliser avant que setUserVisibleHint() été setUserVisibleHint() . Pour contourner ce onCreate vous pouvez définir la valeur de la méthode onCreate comme ceci.

 public void onCreate(@Nullable Bundle savedInstanceState) { setUserVisibleHint(false); 

La réponse obsolète:

Dans la plupart des cas d’utilisation, ViewPager n’affiche qu’une page à la fois, mais les fragments pré-mis en cache sont également mis à l’état “visible” (en réalité invisible) si vous utilisez FragmentStatePagerAdapter dans Android Support Library pre-r11 .

Je remplace:

 public class MyFragment extends Fragment { @Override public void setMenuVisibility(final boolean visible) { super.setMenuVisibility(visible); if (visible) { // ... } } // ... } 

Pour capturer l’état de focus de fragment, ce qui est selon moi l’état le plus approprié de la “visibilité”, puisque seul un fragment de ViewPager peut réellement placer ses éléments de menu avec les éléments de l’activité parent.

Comment déterminer quand Fragment devient visible dans ViewPager

Vous pouvez effectuer les opérations suivantes en setUserVisibleHint dans votre Fragment :

 public class MyFragment extends Fragment { @Override public void setUserVisibleHint(boolean isVisibleToUser) { super.setUserVisibleHint(isVisibleToUser); if (isVisibleToUser) { } else { } } } 

Cela semble restaurer le onResume() normal de onResume() auquel vous vous attendez. Il joue bien en appuyant sur la touche d’accueil pour quitter l’application, puis ré-entrer dans l’application. onResume() n’est pas appelé deux fois de suite.

 @Override public void setUserVisibleHint(boolean visible) { super.setUserVisibleHint(visible); if (visible && isResumed()) { //Only manually call onResume if fragment is already visible //Otherwise allow natural fragment lifecycle to call onResume onResume(); } } @Override public void onResume() { super.onResume(); if (!getUserVisibleHint()) { return; } //INSERT CUSTOM CODE HERE } 

Voici une autre façon d’utiliser onPageChangeListener :

  ViewPager pager = (ViewPager) findByViewId(R.id.viewpager); FragmentPagerAdapter adapter = new FragmentPageAdapter(getFragmentManager); pager.setAdapter(adapter); pager.setOnPageChangeListener(new OnPageChangeListener() { public void onPageSelected(int pageNumber) { // Just define a callback method in your fragment and call it like this! adapter.getItem(pageNumber).imVisible(); } public void onPageScrolled(int arg0, float arg1, int arg2) { // TODO Auto-generated method stub } public void onPageScrollStateChanged(int arg0) { // TODO Auto-generated method stub } }); 

setUserVisibleHint() est appelé parfois avant onCreateView() et parfois après cela provoque des problèmes.

Pour surmonter cela, vous devez également vérifier isResumed() dans la méthode setUserVisibleHint() . Mais dans ce cas, j’ai réalisé que setUserVisibleHint() n’est appelé que si Fragment est repris et visible, PAS lorsqu’il est créé.

Donc, si vous voulez mettre à jour quelque chose quand Fragment est visible , mettez votre fonction de mise à jour à la fois dans onCreate() et setUserVisibleHint() :

 @Override public View onCreateView(...){ ... myUIUpdate(); ... } .... @Override public void setUserVisibleHint(boolean visible){ super.setUserVisibleHint(visible); if (visible && isResumed()){ myUIUpdate(); } } 

MISE À JOUR: J’ai myUIUpdate() réalisé que myUIUpdate() est parfois appelé deux fois, la raison est que si vous avez 3 tabs et que ce code est sur le 2ème onglet, vous ouvrez le 1er onglet pour la première myUIUpdate() est appelée. Ensuite, lorsque vous faites glisser vers le 2e onglet, myUIUpdate() de if (visible && isResumed()) est appelé et, par conséquent, myUIUpdate() peut être appelé deux fois par seconde.

L’autre problème est !visible dans setUserVisibleHint est appelé à la fois 1) lorsque vous sortez de l’écran fragment et 2) avant sa création, lorsque vous passez à la première fois sur l’écran fragmenté.

Solution:

 private boolean fragmentResume=false; private boolean fragmentVisible=false; private boolean fragmentOnCreated=false; ... @Override public View onCreateView(...){ ... //Initialize variables if (!fragmentResume && fragmentVisible){ //only when first time fragment is created myUIUpdate(); } ... } @Override public void setUserVisibleHint(boolean visible){ super.setUserVisibleHint(visible); if (visible && isResumed()){ // only at fragment screen is resumed fragmentResume=true; fragmentVisible=false; fragmentOnCreated=true; myUIUpdate(); }else if (visible){ // only at fragment onCreated fragmentResume=false; fragmentVisible=true; fragmentOnCreated=true; } else if(!visible && fragmentOnCreated){// only when you go out of fragment screen fragmentVisible=false; fragmentResume=false; } } 

Explication:

fragmentResume , fragmentVisible : s’assure que myUIUpdate() dans onCreateView() est appelée uniquement lorsque fragment est créé et visible, pas lors de la reprise. Il résout également le problème lorsque vous êtes au premier onglet, le deuxième onglet est créé même s’il n’est pas visible. Cela résout cela et vérifie si l’écran de fragment est visible lorsque onCreate .

fragmentOnCreated : s’assure que fragment n’est pas visible et n’est pas appelé lors de la première création de fragment. Alors maintenant, cette clause if est appelée uniquement lorsque vous sortez de fragment.

Mise à jour Vous pouvez mettre tout ce code dans le code BaseFragment comme ceci et remplacer la méthode.

 package com.example.com.ui.fragment; import android.os.Bundle; import android.support.annotation.Nullable; import android.support.v4.app.Fragment; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import com.example.com.R; public class SubscribeFragment extends Fragment { @Override public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { View view = inflater.inflate(R.layout.fragment_subscribe, container, false); return view; } @Override public void setUserVisibleHint(boolean isVisibleToUser) { super.setUserVisibleHint(isVisibleToUser); if (isVisibleToUser) { // called here } } @Override public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); } } 

Remplacez setPrimaryItem() dans la sous-classe FragmentPagerAdapter . J’utilise cette méthode et ça marche bien.

 @Override public void setPrimaryItem(ViewGroup container, int position, Object object) { // This is what calls setMenuVisibility() on the fragments super.setPrimaryItem(container, position, object); if (object instanceof MyWhizBangFragment) { MyWhizBangFragment fragment = (MyWhizBangFragment) object; fragment.doTheThingYouNeedToDoOnBecomingVisible(); } } 

Remplacez Fragment.onHiddenChanged() pour cela.

public void onHiddenChanged(boolean hidden)

Appelé lorsque l’état caché (tel que renvoyé par isHidden() ) du fragment a été modifié. Les fragments ne sont pas cachés. cela sera appelé chaque fois que le fragment change d’état.

Paramètres
hiddenboolean : True si le fragment est maintenant masqué, false s’il n’est pas visible.

Pour détecter le Fragment dans ViewPager visible, je suis certain que l’ utilisation de setUserVisibleHint n’est pas suffisante.
Voici ma solution pour vérifier les fragments visibles, invisibles lors du premier lancement de viewpager, passer d’une page à l’autre, aller dans une autre activité / fragment / background / foreground`

 public class BaseFragmentHelpLoadDataWhenVisible extends Fragment { protected boolean mIsVisibleToUser; // you can see this variable may absolutely <=> getUserVisibleHint() but it not. Currently, after many test I find that /** * This method will call when viewpager create fragment and when we go to this fragment from * background or another activity, fragment * NOT call when we switch between each page in ViewPager */ @Override public void onStart() { super.onStart(); if (mIsVisibleToUser) { onVisible(); } } @Override public void onStop() { super.onStop(); if (mIsVisibleToUser) { onInVisible(); } } /** * This method will call at first time viewpager created and when we switch between each page * NOT called when we go to background or another activity (fragment) when we go back */ @Override public void setUserVisibleHint(boolean isVisibleToUser) { super.setUserVisibleHint(isVisibleToUser); mIsVisibleToUser = isVisibleToUser; if (isResumed()) { // fragment have created if (mIsVisibleToUser) { onVisible(); } else { onInVisible(); } } } public void onVisible() { Toast.makeText(getActivity(), TAG + "visible", Toast.LENGTH_SHORT).show(); } public void onInVisible() { Toast.makeText(getActivity(), TAG + "invisible", Toast.LENGTH_SHORT).show(); } } 

EXPLICATION Vous pouvez vérifier le logcat ci-dessous avec soin alors je pense que vous pouvez savoir pourquoi cette solution fonctionnera

Premier lancement

 Fragment1: setUserVisibleHint: isVisibleToUser=false isResumed=false Fragment2: setUserVisibleHint: isVisibleToUser=false isResumed=false Fragment3: setUserVisibleHint: isVisibleToUser=false isResumed=false Fragment1: setUserVisibleHint: isVisibleToUser=true isResumed=false // AT THIS TIME isVisibleToUser=true but fragment still not created. If you do something with View here, you will receive exception Fragment1: onCreateView Fragment1: onStart mIsVisibleToUser=true Fragment2: onCreateView Fragment3: onCreateView Fragment2: onStart mIsVisibleToUser=false Fragment3: onStart mIsVisibleToUser=false 

Aller à la page 2

 Fragment1: setUserVisibleHint: isVisibleToUser=false isResumed=true Fragment2: setUserVisibleHint: isVisibleToUser=true isResumed=true 

Aller à la page3

 Fragment2: setUserVisibleHint: isVisibleToUser=false isResumed=true Fragment3: setUserVisibleHint: isVisibleToUser=true isResumed=true 

Aller en arrière-plan:

 Fragment1: onStop mIsVisibleToUser=false Fragment2: onStop mIsVisibleToUser=false Fragment3: onStop mIsVisibleToUser=true 

Aller au premier plan

 Fragment1: onStart mIsVisibleToUser=false Fragment2: onStart mIsVisibleToUser=false Fragment3: onStart mIsVisibleToUser=true 

Projet DEMO ici

J’espère que ça aide

J’ai compris que les méthodes onCreateOptionsMenu et onPrepareOptionsMenu ne sont appelées que dans le cas où le fragment est vraiment visible. Je n’ai pas trouvé de méthode qui se comporte comme celles-ci, j’ai également essayé OnPageChangeListener mais cela n’a pas fonctionné pour les situations, par exemple, j’ai besoin d’une variable initialisée dans la méthode onCreate .

Ces deux méthodes peuvent donc être utilisées pour résoudre ce problème, en particulier pour les travaux de petite taille et les tâches courtes.

Je pense que c’est la meilleure solution mais pas la meilleure. Je vais utiliser cela mais attendez une meilleure solution en même temps.

Cordialement.

Une autre solution affichée ici, remplaçant setPrimaryItem dans le pageradapter par kris larson, a presque fonctionné pour moi. Mais cette méthode est appelée plusieurs fois pour chaque configuration. J’ai aussi eu des NPE de vues, etc. dans le fragment car ce n’est pas prêt les premiers temps où cette méthode est appelée. Avec les modifications suivantes, cela a fonctionné pour moi:

 private int mCurrentPosition = -1; @Override public void setPrimaryItem(ViewGroup container, int position, Object object) { super.setPrimaryItem(container, position, object); if (position == mCurrentPosition) { return; } if (object instanceof MyWhizBangFragment) { MyWhizBangFragment fragment = (MyWhizBangFragment) object; if (fragment.isResumed()) { mCurrentPosition = position; fragment.doTheThingYouNeedToDoOnBecomingVisible(); } } } 

Ajouter le code suivant à l’intérieur du fragment

 @Override public void setMenuVisibility(final boolean visible) { super.setMenuVisibility(visible); if (visible && isResumed()) { } } 

Détection par focused view !

Cela fonctionne pour moi

 public static boolean isFragmentVisible(Fragment fragment) { Activity activity = fragment.getActivity(); View focusedView = fragment.getView().findFocus(); return activity != null && focusedView != null && focusedView == activity.getWindow().getDecorView().findFocus(); } 

Notez que setUserVisibleHint(false) n’est pas appelé sur l’activité / fragment stop. Vous devrez toujours cocher Démarrer / Arrêter pour register/unregister correctement les écouteurs / etc.

De plus, vous obtiendrez setUserVisibleHint(false) si votre fragment démarre dans un état non visible. vous ne voulez pas vous unregister car vous ne vous êtes jamais enregistré auparavant dans ce cas.

 @Override public void onStart() { super.onStart(); if (getUserVisibleHint()) { // register } } @Override public void onStop() { if (getUserVisibleHint()) { // unregister } super.onStop(); } @Override public void setUserVisibleHint(boolean isVisibleToUser) { super.setUserVisibleHint(isVisibleToUser); if (isVisibleToUser && isResumed()) { // register if (!mHasBeenVisible) { mHasBeenVisible = true; } } else if (mHasBeenVisible){ // unregister } } 

J’ai rencontré ce problème lorsque j’essayais de déclencher une timer lorsque le fragment du visionneur était affiché à l’écran.

La timer a toujours commencé juste avant que le fragment soit vu par l’utilisateur. C’est parce que la méthode onResume() dans le fragment est appelée avant que nous puissions voir le fragment.

Ma solution était de faire une vérification dans la méthode onResume() . Je voulais appeler une certaine méthode ‘foo ()’ quand le fragment 8 était le fragment actuel des pagers de vue.

 @Override public void onResume() { super.onResume(); if(viewPager.getCurrentItem() == 8){ foo(); //Your code here. Executed when fragment is seen by user. } } 

J’espère que cela t’aides. J’ai vu ce problème surgir beaucoup. Cela semble être la solution la plus simple que j’ai vue. Beaucoup d’autres ne sont pas compatibles avec les API inférieures, etc.

J’ai eu le même problème. ViewPager exécute d’autres événements de cycle de vie de fragment et je n’ai pas pu modifier ce comportement. J’ai écrit un simple pager en utilisant des fragments et des animations disponibles. SimplePager

J’ai rencontré le même problème en travaillant avec FragmentStatePagerAdapters et 3 tabs. Je devais montrer un Dilaog chaque fois que le premier onglet était cliqué et le cacher en cliquant sur d’autres tabs.

Le setUserVisibleHint() seul n’a pas aidé à trouver le fragment visible actuel.

En cliquant sur le 3ème onglet —–> 1er onglet. Il s’est déclenché deux fois pour le 2ème fragment et pour le 1er fragment. Je l’ai combiné avec la méthode isResumed ().

  @Override public void setUserVisibleHint(boolean isVisibleToUser) { super.setUserVisibleHint(isVisibleToUser); isVisible = isVisibleToUser; // Make sure that fragment is currently visible if (!isVisible && isResumed()) { // Call code when Fragment not visible } else if (isVisible && isResumed()) { // Call code when Fragment becomes visible. } } 

Nous avons un cas particulier avec MVP où le fragment doit informer le présentateur que la vue est devenue visible et que le présentateur est injecté par Dagger dans fragment.onAttach() .

setUserVisibleHint() ne suffit pas, nous avons détecté 3 cas différents qui devaient être traités ( onAttach() est mentionné pour que vous sachiez quand le présentateur est disponible):

  1. Un fragment vient d’être créé. Le système effectue les appels suivants:

     setUserVisibleHint() // before fragment's lifecycle calls, so presenter is null onAttach() ... onResume() 
  2. Le fragment déjà créé et le bouton d’accueil sont enfoncés. Lors de la restauration de l’application au premier plan, cela s’appelle:

     onResume() 
  3. Changement d’orientation:

     onAttach() // presenter available onResume() setUserVisibleHint() 

Nous voulons seulement que l’indicateur de visibilité parvienne au présentateur une seule fois. Voici comment procéder:

 @Nullable @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View root = inflater.inflate(R.layout.fragment_list, container, false); setHasOptionsMenu(true); if (savedInstanceState != null) { lastOrientation = savedInstanceState.getInt(STATE_LAST_ORIENTATION, getResources().getConfiguration().orientation); } else { lastOrientation = getResources().getConfiguration().orientation; } return root; } @Override public void onResume() { super.onResume(); presenter.onResume(); int orientation = getResources().getConfiguration().orientation; if (orientation == lastOrientation) { if (getUserVisibleHint()) { presenter.onViewBecomesVisible(); } } lastOrientation = orientation; } @Override public void setUserVisibleHint(boolean isVisibleToUser) { super.setUserVisibleHint(isVisibleToUser); if (presenter != null && isResumed() && isVisibleToUser) { presenter.onViewBecomesVisible(); } } @Override public void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); outState.putInt(STATE_LAST_ORIENTATION, lastOrientation); } 

Essayez ceci, c’est du travail pour moi:

 @Override public void onHiddenChanged(boolean hidden) { super.onHiddenChanged(hidden); if (hidden) { }else {} } 

J’ai remplacé la méthode Count du FragmentStatePagerAdapter associé et lui ai renvoyé le nombre total moins le nombre de pages à masquer:

  public class MyAdapter : Android.Support.V13.App.FragmentStatePagerAdapter { private List _fragments; public int TrimmedPages { get; set; } public MyAdapter(Android.App.FragmentManager fm) : base(fm) { } public MyAdapter(Android.App.FragmentManager fm, List fragments) : base(fm) { _fragments = fragments; TrimmedPages = 0; } public override int Count { //get { return _fragments.Count; } get { return _fragments.Count - TrimmedPages; } } } 

Donc, s’il y a 3 fragments initialement ajoutés à ViewPager, et que seuls les 2 premiers doivent être affichés jusqu’à ce qu’une condition soit remplie, remplacez le nombre de pages en définissant TrimmedPages sur 1 et il ne doit afficher que les deux premières pages.

Cela fonctionne bien pour les pages à la fin, mais ne sera pas vraiment utile pour ceux au début ou au milieu (bien qu’il y ait beaucoup de façons de le faire).