Comment déterminer quand Fragment devient visible dans ViewPager

754

Problème: Fragment onResume()en ViewPagerest tiré avant que le fragment devient réellement visible.

Par exemple, j'ai 2 fragments avec ViewPageret FragmentPagerAdapter. Le deuxième fragment n'est disponible que 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 dialogue d'alerte).

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

L' onResume()événement est donc déclenché dans le deuxième fragment bien avant qu'il ne devienne visible. C'est pourquoi j'essaie de trouver un événement qui se déclenche lorsque le deuxième fragment devient visible pour afficher une boîte de dialogue au moment approprié.

Comment cela peut-il être fait?

4ntoine
la source
18
"J'ai 2 fragments avec ViewPager et FragmentPagerAdapter. Le deuxième fragment peut être disponible uniquement pour les utilisateurs autorisés et je dois demander à me connecter lorsque le fragment devient visible (boîte de dialogue d'alerte)." - À mon humble avis, c'est horrible UX. Ouvrir une boîte de dialogue parce que l'utilisateur a glissé horizontalement me ferait vous donner une note d'une étoile sur le Play Store.
CommonsWare
est-il préférable d'afficher simplement les informations sur TextView avec le bouton "Connexion"? Quelle est votre solution dans ce cas?
4ntoine
5
"Pas besoin de charger des données si elles ne seront pas affichées." - alors vous ne devriez pas le mettre dans un fichier ViewPager. Dans un téléavertisseur de deux pages, les deux pages seront chargées immédiatement, que cela vous plaise ou non. L'expérience utilisateur de ViewPagerest censée être que le contenu est là immédiatement après avoir glissé, pas un peu plus tard. C'est pourquoi ViewPagerinitialise une page avant ce qui est visible, pour garantir cette expérience utilisateur.
CommonsWare
2
Il semble que ViewPager ne soit pas assez flexible et il ne permet pas de désactiver la mise en cache, car setOffscreenPageLimit minimum est 1: stackoverflow.com/questions/10073214/… . Je ne vois aucune raison à cela et le comportement attendu (en cas de mise en cache obligatoire) est de créer le fragment MAIS onResume () du fragment de feu lorsque le fragment devient visible.
4ntoine
1
Un peu tard, mais pour toute personne confrontée au même problème, vous pouvez essayer la bibliothèque FragmentViewPager (je suis l'auteur), qui traite de ce problème et fournit quelques fonctionnalités supplémentaires. Pour un exemple, consultez la page GitHub du projet ou cette réponse stackoverflow .
S.Brukhanda

Réponses:

581

Comment déterminer quand Fragment devient visible dans ViewPager

Vous pouvez effectuer les opérations suivantes en remplaçant setUserVisibleHintvotre Fragment:

public class MyFragment extends Fragment {
    @Override
    public void setUserVisibleHint(boolean isVisibleToUser) {
        super.setUserVisibleHint(isVisibleToUser);
        if (isVisibleToUser) {
        }
        else {
        }
    }
}
gorn
la source
7
Grâce à la mise à jour de la bibliothèque de support Android d'aujourd'hui (rev 11), le problème des indices visibles par l'utilisateur est enfin résolu. Il est désormais sûr d'utiliser un indice visible par l'utilisateur pour ViewPager.
Oasis Feng,
58
J'ai constaté que la méthode setUserVisibleHint est appelée AVANT l'appel de onCreateView, ce qui rend difficile le suivi de toute initialisation.
AndroidDev
11
@AndroidDev Si vous voulez exécuter du code lorsque l'indication est vraie, mais qui a besoin de l'arborescence de vue déjà initialisée, enveloppez simplement ce bloc de code isResumed()pour éviter un NPE. Ça marche bien pour moi.
Ravi Thapliyal
13
C'est juste ridicule qu'il y ait tellement de hacks différents pour quelque chose que le SDK devrait fournir par défaut.
Mike6679
15
setUserVisibleHintest désormais obsolète
SR
525

MISE À JOUR : Android Support Library (rev 11) a finalement corrigé le problème de l'indice visible de l'utilisateur , maintenant si vous utilisez la bibliothèque de support pour les fragments, vous pouvez utiliser getUserVisibleHint()ou remplacer en toute sécurité setUserVisibleHint()pour capturer les modifications comme décrit par la réponse de Gorn.

MISE À JOUR 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 peut donc y avoir un problème lorsque vous essayez de l'utiliser avant qu'il setUserVisibleHint()soit invoqué. Pour contourner ce problème, vous pouvez définir une valeur dans une onCreateméthode comme celle-ci.

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

La réponse obsolète:

Dans la plupart des cas d'utilisation, ViewPageraffichez une seule page à la fois, mais les fragments pré-mis en cache sont également mis à l'état "visible" (en fait invisible) si vous utilisez FragmentStatePagerAdapterin 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 du fragment, ce qui, à mon avis, est l'état le plus approprié de la "visibilité", car un seul fragment dans ViewPager peut réellement placer ses éléments de menu avec les éléments de l'activité parent.

Oasis Feng
la source
73
Attention à utiliser getActivity (), au premier démarrage, il sera nul.
vovkab
10
Notez que setUserVisibleHint () a toujours fonctionné correctement dans FragmentPagerAdapter.
louielouie
8
Cette solution (et aussi celle de Gorn) est un peu en retard. Dans les cas où le viseur est balayé rapidement et plusieurs fois, cette méthode sera appelée avec un délai (lorsque le fragment n'est plus visible / invisible).
AsafK
3
Je reçois truepour getUserVisibleHint () quand onCreateOptionsMenuest appelé, et quand setUserVisibleHintest appelé, le menu n'a pas encore été créé semble-t-il. En fin de compte, deux options sont ajoutées au menu lorsque je ne veux que le menu du fragment visible. Des suggestions à cet égard?
cYrixmorten
13
setUserVisibleHintest désormais obsolète
SR
143

Cela semble restaurer le onResume()comportement normal que vous attendez. Cela joue bien en appuyant sur la touche d'accueil pour quitter l'application, puis en entrant à nouveau 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
}
craigrs84
la source
4
Exactement ce que je cherchais. Résout le problème d' setUserVisibleHintêtre appelé avant onCreateViewet qui setUserVisibleHintn'est pas appelé si l'application passe en arrière-plan puis au premier plan. Impressionnant! Je vous remercie!
Alex
11
Je pense qu'appeler sur Reprendre soi-même est une assez mauvaise idée, mais sinon, tout ce dont vous avez besoin pour répondre à la question de ce message est dans la fonction setUserVisibleHint!
Quentin G.
4
Je préfère implémenter une onVisibleToUser()méthode simple et faire appel de onResume()et vers setUserVisibleHint(boolean)au lieu de onResume()m'appeler et d'interférer avec les rappels du cycle de vie. Sinon, je pense que cette approche fonctionne bien, merci!
Stephan Henningsen
1
Oui, je suis d'accord avec les commentaires ci-dessus. En général, essayez d'éviter d'appeler des méthodes publiques à partir de méthodes publiques de votre classe. Créez une méthode privée et appelez-la à partir des deux méthodes publiques.
Johan Franzén
4
setUserVisibleHint déconseillé!
Hamid Reza
70

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

  }
});
Ostkontentitan
la source
1
Cette solution fonctionne bien, merci. Cela devrait être la solution recommandée pour ceux qui construisent pour des plates-formes plus anciennes utilisant les packages de support de fragment (v4)
Frank Yin
7
Il convient de noter que cela ne pourrait pas vous donner le résultat attendu si vous utilisez un FragmentStatePagerAdapter et instanciez une nouvelle instance de Fragment sur getItem (), car vous ne définiriez que l'état sur le nouveau fragment et non celui que le viewpager a obtenu
Ben Pearson
1
Cette solution est bonne si vous souhaitez effectuer quelque chose lorsque le fragment devient visible. Il ne garde pas l'état visible d'un fragment (ce qui signifie qu'il ne sait pas quand le fragment est invisible).
AsafK
Je suis du même type de problème. veuillez m'aider à résoudre le problème. Mon message: stackoverflow.com/questions/23115283/…
Jeeten Parmar
cela donne une exception de pointeur nul pour l'adaptateur que j'utilise dans la méthode iamVisible du fragment. J'essaie de définir les données reçues du réseau uniquement lorsque le fragment est visible.
zaphod100.10
58

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

Pour surmonter cela, vous devez également vérifier la méthode isResumed()interne setUserVisibleHint(). Mais dans ce cas, j'ai réalisé que je ne suis setUserVisibleHint()appelé que si le fragment est repris et visible, PAS lors de sa création.

Donc, si vous souhaitez mettre à jour quelque chose lorsque 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: Je me suis quand même rendu compte que je suis myUIUpdate()appelé parfois deux fois, la raison en est que si vous avez 3 onglets et que ce code est sur le 2e onglet, lorsque vous ouvrez le 1er onglet pour la première fois, le 2e onglet est également créé même s'il n'est pas visible et myUIUpdate()est appelé. Ensuite, lorsque vous glissez vers le 2e onglet, myUIUpdate()de if (visible && isResumed())est appelé et, par conséquent, myUIUpdate()peut être appelé deux fois en une seconde.

L'autre problème est !visibledans setUserVisibleHintobtient appelé à la fois 1) lorsque vous sortez de l'écran de fragment et 2) avant qu'il ne soit créé, lorsque vous passez à l'écran de fragment la 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()in onCreateView()n'est appelé que lorsque le fragment est créé et visible, et non à la reprise. Il résout également le problème lorsque vous êtes au 1er 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 quand onCreate.

fragmentOnCreated: S'assure que le fragment n'est pas visible et n'est pas appelé lorsque vous créez un fragment pour la première fois. Alors maintenant, cette clause if n'est appelée que lorsque vous glissez hors du fragment.

Mise à jour Vous pouvez mettre tout ce code dans du BaseFragmentcode comme celui-ci et remplacer la méthode.

Jemshit Iskenderov
la source
1
Votre solution fonctionne parfaitement, sauf avec un scénario, lorsque le fragment est ouvert normalement, puis vous cliquez sur un bouton pour le remplacer par un autre fragment, puis cliquez sur retour pour faire apparaître le nouveau fragment à partir du backstack, si ce cas setUserVisibleHintn'a pas été appelé. ! et à l'intérieur de la onCreateViewméthode fragmentVisibleétait false!? donc le fragment est apparu vide ..! Des pensées.?
Alaa AbuZarifa
28

Pour détecter Fragmenten ViewPagervisible, je suis bien sûr que seule l'utilisation setUserVisibleHint n'est pas suffisante.
Voici ma solution pour vérifier si un fragment est visible ou invisible. Tout d'abord lors du lancement de Viewpager, basculez entre les pages, accédez à une autre activité / fragment / arrière-plan / premier plan`

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 be called when viewpager creates fragment and when we go to this fragment background or another activity or fragment
     * NOT called 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 called 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 attentivement le logcat ci-dessous, puis je pense que vous savez peut-être 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 page2

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 à l'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

Phan Van Linh
la source
1
Cela ne fonctionne pas lorsqu'un fragment a un autre téléavertisseur de vue qui comprend également un fragment.
Shihab Uddin
désolé, je ne peux pas poster la réponse pour le moment car je n'ai pas assez de temps, si possible, veuillez référer mon git à propos de votre problème ici github.com/PhanVanLinh/AndroidViewPagerSkeleton/tree/master . SubChildContainerFragmentest utilisé pour détecter a fragment has another view pager which also consists fragment. Vous pouvez mélanger SubChildContainerFragmentet ChildContainerFragmentà 1 classe. J'espère que cela vous aidera. Je posterai la réponse complète plus tard
Phan Van Linh
26
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);
    }
}
w3hacker
la source
2
Ce devrait être la réponse acceptée. Fonctionne également avec android.app.Fragment, ce qui est un énorme bonus.
RajV
setUserVisibleHintObsolète
ekashking
23

Dans ViewPager2et à ViewPagerpartir de la version, androidx.fragment:fragment:1.1.0vous pouvez simplement utiliser onPauseet des onResumerappels pour déterminer quel fragment est actuellement visible pour l'utilisateur. onResumele rappel est appelé lorsque le fragment est devenu visible et onPauselorsqu'il cesse d'être visible.

Dans le cas de ViewPager2, il s'agit d'un comportement par défaut, mais le même comportement peut être activé ViewPagerfacilement pour l'ancien .

Pour activer ce comportement dans le premier ViewPager, vous devez passer le FragmentPagerAdapter.BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENTparamètre comme deuxième argument du FragmentPagerAdapterconstructeur.

FragmentPagerAdapter(fragmentManager, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT)

Remarque: la setUserVisibleHint()méthode et le FragmentPagerAdapterconstructeur avec un paramètre sont désormais obsolètes dans la nouvelle version de Fragment d'Android Jetpack.

lukjar
la source
1
Merci pour la bonne solution. Je cherchais cela depuis longtemps. Great Work
Anurag Srivastava
1
Pour ViewPager2, l'option BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT n'est pas le comportement par défaut, vous devez le définir manuellement. Lorsque vous définissez cette option, votre solution a fonctionné pour moi. Merci.
driAn
1
À partir d'APR 2020, c'est la solution mise à jour et fonctionne comme un charme.
Prakash
1
@ 4ntoine s'il vous plaît envisager d'accepter cette réponse comme la bonne réponse, car pour l'instant c'est correct et la plupart des réponses utilisent la méthode obsolète setUserVisibleHint
Jorn Rigter
16

Remplacer setPrimaryItem()dans la FragmentPagerAdaptersous - classe. J'utilise cette méthode, et cela 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();
    }
}
kris larson
la source
1
Cela a presque fonctionné mais a eu des problèmes avec NPE et a été appelé plusieurs fois. Publié une alternative ci-dessous!
Gober
@Gober sauvegarde l'objet Object à la première fois, et vérifie et ignore l'appel de suivi avec le même objet Object comme le fait FragmentStateAdapter dans la méthode setPrimaryItem ()
wanglugao
12

Remplacez Fragment.onHiddenChanged()pour cela.

public void onHiddenChanged(boolean hidden)

Appelé lorsque l'état caché (tel que renvoyé par isHidden()) du fragment a changé. Les fragments commencent non cachés; cela sera appelé chaque fois que le fragment change d'état.

Paramètres
hidden- boolean: Vrai si le fragment est maintenant masqué, faux s'il n'est pas visible.

dan
la source
3
cette méthode est désormais obsolète et ne peut plus être utilisée
blue while
4
@blue while où voyez-vous qu'il est obsolète? developer.android.com/reference/android/app/...
Cel
1
Je viens de l'utiliser avec succès et la documentation ne semble pas indiquer qu'elle est obsolète.
Trevor
1
@blue pendant ce temps, dans 4.1 (api 16), il n'est pas encore obsolète.
dan
8
J'utilise l'API niveau 19 et je peux confirmer que même si ce n'est pas obsolète, cela ne fonctionne pas comme annoncé. onHiddenChanged n'est pas appelé lorsque le fragment est masqué par un autre fragment, par exemple.
4

J'ai compris cela onCreateOptionsMenuet les onPrepareOptionsMenuméthodes appelées uniquement dans le cas du fragment vraiment visible. Je n'ai trouvé aucune méthode qui se comporte comme celles-ci, j'ai également essayé OnPageChangeListenermais cela n'a pas fonctionné pour les situations, par exemple, j'ai besoin d'une variable initialisée dans onCreatemethod.

Ces deux méthodes peuvent donc être utilisées pour ce problème comme solution de contournement, en particulier pour les petits et courts travaux.

Je pense que c'est la meilleure solution mais pas la meilleure. Je vais l'utiliser, mais attendre une meilleure solution en même temps.

Cordialement.

ismailarilik
la source
2

Une autre solution publié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 également obtenu NPE des vues, etc. dans le fragment car ce n'est pas prêt les premières fois que 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();
        }
    }
}
Gober
la source
2

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

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

     }
}
Afzaal Iftikhar
la source
Cela ne fonctionne que pour ViewPagerFragments. Pas pour les fragments en activité normale.
AndroidGuy
2

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

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

En cliquant sur à partir du 3e 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.
    }

}
Aman Arora
la source
2

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 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. Le 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. Fragment déjà créé et bouton d'accueil enfoncé. 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 que l'indicateur de visibilité ne parvienne au présentateur qu'une seule fois, alors 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);
}
Épingle
la source
2

Détection par focused view!

Ça marche 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();
}
Mohammad Reza Norouzi
la source
1

J'ai rencontré ce problème lorsque j'essayais de déclencher une minuterie lorsque le fragment du viseur était à l'écran pour que l'utilisateur puisse le voir.

Le minuteur a toujours démarré juste avant que le fragment ne soit vu par l'utilisateur. C'est parce que la onResume()méthode dans le fragment est appelée avant que nous puissions voir le fragment.

Ma solution a été de vérifier la onResume()méthode. Je voulais appeler une certaine méthode 'foo ()' lorsque 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 apparaître souvent. 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.

Malone
la source
1

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

Rukmal Dias
la source
1

Je l'ai utilisé et cela a fonctionné!

mContext.getWindow().getDecorView().isShown() //boolean
Ali Akram
la source
1

Je supporte SectionsPagerAdapter avec des fragments enfants, donc après beaucoup de maux de tête, j'ai finalement obtenu une version de travail basée sur des solutions de ce sujet:

public abstract class BaseFragment extends Fragment {

    private boolean visible;
    private boolean visibilityHintChanged;

    /**
     * Called when the visibility of the fragment changed
     */
    protected void onVisibilityChanged(View view, boolean visible) {

    }

    private void triggerVisibilityChangedIfNeeded(boolean visible) {
        if (this.visible == visible || getActivity() == null || getView() == null) {
            return;
        }
        this.visible = visible;
        onVisibilityChanged(getView(), visible);
    }

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        if (!visibilityHintChanged) {
            setUserVisibleHint(false);
        }
    }

    @Override
    public void onResume() {
        super.onResume();
        if (getUserVisibleHint() && !isHidden()) {
            triggerVisibilityChangedIfNeeded(true);
        }
    }

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

    @Override
    public void setUserVisibleHint(boolean isVisibleToUser) {
        super.setUserVisibleHint(isVisibleToUser);
        visibilityHintChanged = true;
        if (isVisibleToUser && isResumed() && !isHidden()) {
            triggerVisibilityChangedIfNeeded(true);
        } else if (!isVisibleToUser) {
            triggerVisibilityChangedIfNeeded(false);
        }
    }

    @Override
    public void onPause() {
        super.onPause();
        triggerVisibilityChangedIfNeeded(false);
    }

    @Override
    public void onStop() {
        super.onStop();
        triggerVisibilityChangedIfNeeded(false);
    }

    protected boolean isReallyVisible() {
        return visible;
    }
}
Andoctorey
la source
Oublié de le mentionner, une fois que vous avez ajouté un fragment enfant à l'ensemble de fragments parent fragment.setUserVisibleHint (true);
Andoctorey
Et n'oubliez pas d'utiliser getChildFragmentManager () au lieu de getFragmentManager () pour ajouter des fragments enfants.
Andoctorey
0

Notez qu'il setUserVisibleHint(false)n'est pas appelé à l'arrêt de l'activité / du fragment. Vous aurez toujours besoin de vérifier démarrer / arrêter correctement register/unregistertous les écouteurs / etc.

En outre, vous obtiendrez setUserVisibleHint(false)si votre fragment commence dans un état non visible; vous ne voulez pas unregistery aller puisque vous ne vous êtes jamais inscrit 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
    }
}
onze ans
la source
0

Un moyen simple d'implémentation qui consiste à vérifier si l'utilisateur est connecté avant d' accéder au fragment.

Dans votre MainActivity, vous pouvez faire quelque chose comme ça dans la méthode onNavigationItemSelected .

 case R.id.nav_profile_side:


                if (User_is_logged_in) {

                    fragmentManager.beginTransaction()
                            .replace(R.id.content_frame
                                    , new FragmentProfile())
                            .commit();
                }else {

                    ShowLoginOrRegisterDialog(fragmentManager);

                }

                break;

Cependant, si vous utilisez le tiroir de navigation, la sélection dans le tiroir aura changé pour Profile bien que nous ne soyons pas passés au ProfileFragment.

Pour réinitialiser la sélection à la sélection actuelle, exécutez le code ci-dessous

        navigationView.getMenu().getItem(0).setChecked(true);
Martin Mbae
la source
0

setUserVisibleHint (booléen visible) est désormais obsolète. C'est donc la bonne solution

FragmentPagerAdapter(fragmentManager, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT)

Dans ViewPager2 et ViewPager à partir de la version, androidx.fragment:fragment:1.1.0vous pouvez simplement utiliser onPause()et onResume()pour déterminer quel fragment est actuellement visible pour l'utilisateur. onResume()est appelé lorsque le fragment est devenu visible et onPauselorsqu'il cesse d'être visible.

Pour activer ce comportement dans le premier ViewPager, vous devez passer le FragmentPagerAdapter.BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENTparamètre comme deuxième argument du FragmentPagerAdapterconstructeur.

Rahul
la source
-4

J'ai remplacé la méthode Count du FragmentStatePagerAdapter associé et lui ai fait retourner 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; }
     }
 }

Donc, 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, remplacez le nombre de pages en définissant TrimmedPages à 1 et il ne doit afficher que les deux premières pages.

Cela fonctionne bien pour les pages à la fin, mais n'aidera pas vraiment celles du début ou du milieu (bien qu'il existe de nombreuses façons de le faire).

Sam est
la source