Comment déterminer quand Fragment devient visible dans ViewPager

problème: le Fragment onResume() dans ViewPager est tiré avant que le fragment devienne réellement visible.

par exemple, j'ai 2 fragments avec ViewPager et FragmentPagerAdapter . Le second fragment n'est disponible que pour les utilisateurs autorisés et j'ai besoin de demander à l'utilisateur de se connecter lorsque le fragment devient visible (en utilisant un dialogue d'alerte).

mais le ViewPager crée le second fragment lorsque le premier est visible afin de deuxième fragment et le rend visible lorsque l'utilisateur commence à l'utiliser.

ainsi l'événement onResume() est tiré 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 dialogue au moment approprié.

Comment faire?

644
demandé sur Joe Schneider 2012-04-05 11:58:15

21 réponses

mise à jour : Android Support Library (Rév.11) enfin correction de l'indicateur visible par l'utilisateur , maintenant si vous utilisez une bibliothèque de soutien pour les fragments, alors vous pouvez utiliser en toute sécurité getUserVisibleHint() ou outrepasser setUserVisibleHint() pour capturer les changements décrits par la réponse de gorn.

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

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

il pourrait donc y avoir un problème lorsque vous essayez de l'utiliser avant que setUserVisibleHint() ne soit invoqué. Comme solution de contournement, vous pouvez définir la valeur dans onCreate méthode comme ceci.

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

la réponse dépassée:

dans la plupart des cas d'utilisation, ViewPager ne montrent qu'une page à la fois, mais les fragments pré-cache sont également mis à l'état" visible "(en fait 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 mise au point de fragment, qui je pense est l'état le plus approprié de la" visibilité " que vous voulez dire, puisque seul un fragment dans ViewPager peut réellement placer ses éléments de menu ensemble avec les éléments de l'activité de parent.

477
répondu Oasis Feng 2017-04-04 23:42:08

comment déterminer quand Fragment devient visible dans ViewPager

vous pouvez faire ce qui suit en remplaçant setUserVisibleHint dans votre Fragment :

public class MyFragment extends Fragment {
    @Override
    public void setUserVisibleHint(boolean isVisibleToUser) {
        super.setUserVisibleHint(isVisibleToUser);
        if (isVisibleToUser) {
        }
        else {
        }
    }
}
528
répondu gorn 2016-06-15 18:11:35

cela semble restaurer le comportement normal onResume() que 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
}
112
répondu craigrs84 2016-09-27 12:42:51

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

  }
});
57
répondu Ostkontentitan 2016-09-27 12:43:17

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

pour surmonter cela, vous devez cocher isResumed() ainsi qu'à l'intérieur de setUserVisibleHint() méthode. Mais dans ce cas j'ai réalisé que setUserVisibleHint() est appelé seulement si Fragment est repris et visible, pas quand créé.

donc si vous voulez mettre à jour quelque chose quand le 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: Encore, j'ai réalisé myUIUpdate() est appelée deux fois, parfois, la raison en est, si vous avez 3 onglets et ce code est sur le 2ème onglet, lorsque vous ouvrirez le 1er onglet 2ème onglet est également créé, même elle n'est pas visible et myUIUpdate() est appelé. Puis, lorsque vous passez au 2ème onglet, myUIUpdate() de if (visible && isResumed()) est appelé et en conséquence, myUIUpdate() peut être appelé deux fois en une seconde.

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

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 que lorsque le fragment est créé et visible, pas sur le curriculum vitae. Elle résout également un problème lorsque vous êtes au 1er onglet 2ème onglet est créé même si elle n'est pas visible. Cela résout cela et vérifie si l'écran de fragment est visible quand onCreate .

fragmentOnCreated : assure que fragment n'est pas visible, et pas appelé lorsque vous créez fragment première fois. Donc maintenant ceci si clause est seulement appelé quand vous glissez hors de fragment.

mise à Jour Vous pouvez mettre tout ce code dans BaseFragment code comme ceci et la méthode de surpassement.

51
répondu Jemshit Iskenderov 2016-12-01 12:23:06
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);
    }
}
23
répondu w3hacker 2017-01-21 02:33:43

remplacer setPrimaryItem() dans la sous-classe FragmentPagerAdapter . J'utilise cette méthode, et il fonctionne 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();
    }
}
14
répondu kris larson 2016-06-15 18:17:08

Remplacer Fragment.onHiddenChanged() pour que.

public void onHiddenChanged(boolean hidden)

appelé lorsque l'état caché (comme retourné par isHidden() ) du fragment a changé. Les Fragments ne commencent pas cachés; ceci sera appelé à chaque fois que le fragment change d'état.

paramètres

hidden - boolean : vrai si le fragment est maintenant caché, faux s'il n'est pas visible.

10
répondu dan 2017-02-13 10:45:48

pour détecter Fragment dans ViewPager visible, je suis sûr que en utilisant seulement setUserVisibleHint n'est pas suffisant.

Voici ma solution pour check fragment visible, invisible lors du premier lancement viewpager, passer d'une page à l'autre, passer à 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 puis 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

allez à la page2

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

allez à la page3

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

d'Aller au fond:

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

passer à l'avant-plan

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

Démo projet ici

Espoir aide

10
répondu Phan Van Linh 2017-12-13 09:25:06

j'ai compris que onCreateOptionsMenu et onPrepareOptionsMenu méthodes appelées seulement dans le cas du fragment vraiment visible. Je n'ai pu trouver aucune méthode qui se comporte comme ceux-ci, aussi j'ai essayé OnPageChangeListener mais il n'a pas fonctionné pour les situations, par exemple, j'ai besoin d'une variable initialisée dans onCreate méthode.

donc ces deux méthodes peuvent être utilisées pour résoudre ce problème, en particulier pour les petits et les petits travaux.

je pense que c'est la meilleure solution mais pas la meilleure. Je l'utiliserai mais j'attendrai une meilleure solution en même temps.

Cordialement.

4
répondu ismailarilik 2014-06-24 08:20:46

une autre solution posté ici en surpassant setPrimaryItem dans le pageradapter par kris larson presque travaillé pour moi. Mais cette méthode est appelée plusieurs fois pour chaque configuration. Aussi j'ai eu NPE de vues, etc. dans le fragment que ce n'est pas prêt pour les premières fois, cette méthode est appelée. Avec les changements suivants, 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();
        }
    }
}
2
répondu Gober 2017-05-23 12:10:43

ajouter le Code suivant à l'intérieur du fragment

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

     }
}
2
répondu Afzaal Iftikhar 2017-01-02 18:24:31

j'ai rencontré le même problème en travaillant avec FragmentStatePagerAdapters et 3 onglets. J'ai dû montrer un Dilaog chaque fois que le 1er onglet a été cliqué et le cacher sur le fait de cliquer d'autres onglets.

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

en cliquant Du 3ème onglet -----> 1er onglet. Il s'est déclenché deux fois pour le 2e 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.
    }

}
2
répondu Aman Arora 2017-02-13 11:36:31

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

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

  1. Fragment vient d'être créé. Ce système permet à la appels suivants:

    setUserVisibleHint() // before fragment's lifecycle calls, so presenter is null
    onAttach()
    ...
    onResume()
    
  2. Fragment déjà créé et le bouton d'accueil est pressé. Lors de la restauration de l'application à l'avant-plan, cela s'appelle:

    onResume()
    
  3. changement d'Orientation:

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

nous ne voulons que l'indice de visibilité pour atteindre le présentateur une seule fois, donc c'est comme ça que nous le faisons:

@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);
}
2
répondu Pin 2017-05-08 15:30:26

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();
}
2
répondu Mohammad Reza Norouzi 2017-12-19 21:06:32

noter que setUserVisibleHint(false) n'est pas appelé activité / arrêt de fragment. Vous aurez toujours besoin de vérifier démarrer/arrêter pour register/unregister correctement tous les auditeurs/etc.

aussi, vous obtiendrez setUserVisibleHint(false) si votre fragment commence dans un état non visible; vous ne voulez pas unregister là puisque vous n'avez 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
    }
}
1
répondu elevenfive 2016-09-27 12:42:21

j'ai rencontré ce problème lorsque j'ai essayé d'obtenir une minuterie se déclenche lorsque le fragment dans le viewpager était à l'écran pour l'utilisateur de voir.

la minuterie a toujours commencé juste avant que le fragment ne 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 a été de faire un contrôle dans la méthode onResume() . Je voulais appeler une certaine méthode "foo ()" quand fragment 8 était le fragment de courant de View pagers.

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

Espérons que cette aide. J'ai vu ce problème de pop up beaucoup. C'est la solution la plus simple que j'ai vue. Beaucoup d'autres ne sont pas compatibles avec les API inférieures, etc.

1
répondu Malone 2016-09-27 12:42:33

j'ai eu le même problème. ViewPager exécute d'autres événements du cycle de vie des fragments et je ne pouvais pas changer ce comportement. J'ai écrit un pager simple en utilisant des fragments et des animations disponibles. SimplePager

1
répondu Rukmal Dias 2016-09-27 12:43:07

Essayer, c'est du travail pour moi:

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

        if (hidden) {

        }else
        {}
    }
0
répondu Kyo Huu 2018-07-08 05:08:11

j'ai utilisé ceci et ça a marché !

mContext.getWindow().getDecorView().isShown() //boolean
0
répondu Ali Akram 2018-09-21 10:57:28

j'ai remplacé le Comte de la méthode de l'associé FragmentStatePagerAdapter et de l'avoir de retour le nombre total moins le nombre de pages à cacher:

 public class MyAdapter : Android.Support.V13.App.FragmentStatePagerAdapter
 {   
     private List<Fragment> _fragments;

     public int TrimmedPages { get; set; }

     public MyAdapter(Android.App.FragmentManager fm) : base(fm) { }

     public MyAdapter(Android.App.FragmentManager fm, List<Android.App.Fragment> fragments) : base(fm)
     {
         _fragments = fragments;

         TrimmedPages = 0;
     }

     public override int Count
     {
         //get { return _fragments.Count; }
         get { return _fragments.Count - TrimmedPages; }
     }
 }

ainsi, s'il y a 3 fragments initialement ajoutés au ViewPager, et que seuls les 2 premiers doivent être affichés jusqu'à ce qu'une condition soit remplie, Outrepasser le compte de page en réglant les Pageslimmed à 1 et ne montrer que les deux premières pages.

cela fonctionne bien pour les pages sur la fin, mais wont vraiment de l'aide pour ceux au début ou au milieu (bien qu'il y ait beaucoup de façons de le faire).

-3
répondu samis 2013-01-11 21:44:24