Problème: Le fragment onResume()
dans ViewPager
est déclenché avant que le fragment ne devienne 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 dialogue d'alerte).
MAIS la ViewPager
cré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 à balayer.
Ainsi, l'événement onResume()
est 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 opportun.
Comment cela peut-il être fait?
UPDATE: bibliothèque de support Android (rév. 11) enfin correction du problème de l'indicateur visible par l'utilisateur , si vous utilisez la bibliothèque de support pour les fragments, vous pouvez utiliser en toute sécurité getUserVisibleHint()
ou remplacer setUserVisibleHint()
pour capturer les modifications décrit 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 peut donc y avoir un problème lorsque vous essayez de l'utiliser avant que setUserVisibleHint()
ait été appelé. Pour résoudre ce problème, vous pouvez définir une valeur dans 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é-cachés passent également à l'état "visible" (réellement 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 focalisation du fragment, qui est selon moi l’état le plus approprié de la "visibilité" que vous voulez dire, puisqu'un seul fragment dans 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 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 rétablir le comportement onResume()
normal auquel vous vous attendez. Il 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
}
Voici un autre moyen 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 beforeonCreateView()
et parfois après ce qui cause des problèmes.
Pour résoudre ce problème, vous devez également vérifier isResumed()
dans la méthode setUserVisibleHint()
. Mais dans ce cas, j’ai réalisé que setUserVisibleHint()
est appelé seulement si Fragment est repris et visible, PAS lorsqu’il a été créé.
Donc si vous voulez mettre à jour quelque chose quand Fragment vaut 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();
}
}
UPDATE: Pourtant, j'ai réalisé que myUIUpdate()
est appelé deux fois, la raison en est que si vous avez 3 onglets et que ce code est sur le 2ème onglet, lorsque vous ouvrez le 1er onglet pour la première fois, le 2ème onglet est également créé même s'il n'est pas visible et myUIUpdate()
est appelé. Ensuite, lorsque vous passez au 2e onglet, myUIUpdate()
à partir de if (visible && isResumed())
est appelé. 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 à l'écran fragment pour 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()
dans onCreateView()
est appelé uniquement lorsque fragment est créé et visible, et non sur CV. 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 le problème et vérifie si l’écran de fragment est visible lorsque onCreate
.
fragmentOnCreated
: s'assure que le fragment n'est pas visible et qu'il 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 balayez du fragment.
Update Vous pouvez mettre tout ce code dans le code BaseFragment
comme ceci et la méthode override.
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);
}
}
Pour détecter Fragment
dans ViewPager
visible, je suis à peu près sûr que utiliser uniquementsetUserVisibleHint
n'est pas suffisant.
Voici ma solution pour vérifier si un fragment est visible ou invisible. Tout d'abord, lors du lancement de viewpager, changez de page, allez à une autre activité/fragment/fond/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 le logcat ci-dessous avec soin 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
J'espère que ça aide
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 qu'il est renvoyé par
isHidden()
) du fragment a changé. Les fragments commencent pas cachés; cela sera appelé chaque fois que le fragment changera d'état.Paramètres
hidden
-boolean
: Vrai si le fragment est maintenant caché, faux si il n'est pas visible.
J'ai compris que les méthodes onCreateOptionsMenu
et onPrepareOptionsMenu
n'étaient appelées que dans le cas du fragment vraiment visible. Je ne pouvais trouver aucune méthode qui se comporte de la sorte, j'ai aussi 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
.
Donc, ces deux méthodes peuvent être utilisées pour résoudre ce problème comme solution de contournement, en particulier pour les travaux petits et courts.
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.
J'ai rencontré le même problème lorsque je travaillais avec FragmentStatePagerAdapters
et 3 onglets. Je devais montrer un Dilaog chaque fois que le premier onglet était cliqué et le masquer en cliquant sur d'autres onglets.
Remplacer 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.
}
}
Une autre solution affichée ici en remplaçant setPrimaryItem dans le pageradapter de kris Larson a presque fonctionné pour moi. Mais cette méthode est appelée plusieurs fois pour chaque configuration. De plus, j'ai obtenu NPE à partir de vues, etc. dans le fragment, car ce n'est pas prêt les premières fois 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 dans le fragment
@Override
public void setMenuVisibility(final boolean visible)
{
super.setMenuVisibility(visible);
if (visible && isResumed())
{
}
}
Essayez ceci, c'est un travail pour moi:
@Override
public void onHiddenChanged(boolean hidden) {
super.onHiddenChanged(hidden);
if (hidden) {
}else
{}
}
Nous avons un cas spécial 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 à traiter (nous mentionnons onAttach()
pour que vous sachiez quand le présentateur est disponible):
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()
Fragment déjà créé et le bouton d'accueil est enfoncé. Lors de la restauration de l'application au premier plan, cela s'appelle:
onResume()
Changement d'orientation:
onAttach() // presenter available
onResume()
setUserVisibleHint()
Nous souhaitons seulement que l'indicateur de visibilité parvienne une fois au présentateur. C'est pourquoi nous procédons ainsi:
@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étecter 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();
}
Notez que setUserVisibleHint(false)
n'est pas appelé à l'activité/arrêt de fragment. Vous aurez toujours besoin de vérifier le démarrage/arrêt pour que register/unregister
puisse écouter correctement les écouteurs/etc.
De plus, vous obtiendrez setUserVisibleHint(false)
si votre fragment commence dans un état non visible; vous ne voulez pas unregister
car vous ne vous êtes jamais inscrit auparavant.
@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 minuterie lorsque le fragment dans le viewpager était affiché à l'écran.
La minuterie a toujours démarré juste avant que le fragment soit vu par l'utilisateur . C'est parce que la méthode onResume()
du 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 ()' lorsque le fragment 8 était le fragment en cours de la 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 ne pouvais pas changer ce comportement. J'ai écrit un simple pager utilisant des fragments et des animations disponibles . SimplePager
Je supporte SectionsPagerAdapter avec des fragments enfants. Après de nombreux maux de tête, j'ai finalement obtenu une version de travail basée sur les 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;
}
}
Un moyen simple d'implémenter est de vérifier si l'utilisateur est connecté avant aller au fragment.
Dans votre MainActivity, vous pouvez faire quelque chose comme ceci 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;
Toutefois, si vous utilisez le tiroir de navigation, la sélection dans le tiroir aura été modifiée en Profil bien que nous n'ayons pas accès à ProfileFragment.
Pour réinitialiser la sélection à la sélection actuelle, exécutez le code ci-dessous.
navigationView.getMenu().getItem(0).setChecked(true);
Je l'ai utilisé et cela a fonctionné!
mContext.getWindow().getDecorView().isShown() //boolean