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?
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.
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 {
}
}
}
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
}
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 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.
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);
}
}
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();
}
}
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.
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
Espoir aide
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.
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();
}
}
}
ajouter le Code suivant à l'intérieur du fragment
@Override
public void setMenuVisibility(final boolean visible)
{
super.setMenuVisibility(visible);
if (visible && isResumed())
{
}
}
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.
}
}
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):
-
Fragment vient d'être créé. Ce système permet à la appels suivants:
setUserVisibleHint() // before fragment's lifecycle calls, so presenter is null onAttach() ... onResume()
-
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()
-
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);
}
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();
}
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
}
}
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.
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
Essayer, c'est du travail pour moi:
@Override
public void onHiddenChanged(boolean hidden) {
super.onHiddenChanged(hidden);
if (hidden) {
}else
{}
}
j'ai utilisé ceci et ça a marché !
mContext.getWindow().getDecorView().isShown() //boolean
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).