ViewPager ne redessine pas le contenu, reste / devient vide

86

Nous souffrons d'un problème très étrange avec ViewPager ici. Nous intégrons des listes sur chaque page ViewPager et déclenchons notifyDataSetChanged à la fois sur l'adaptateur de liste et l'adaptateur de pager de vue lors de la mise à jour des données de liste.

Ce que nous observons, c'est que parfois, la page ne met pas à jour son arborescence de vues, c'est-à-dire qu'elle reste vierge, ou parfois même disparaît lors de la pagination. Lors de la pagination d'avant en arrière plusieurs fois, le contenu réapparaît soudainement. Il semble qu'Android manque une mise à jour de vue ici. J'ai également remarqué que lors du débogage avec la visionneuse de hiérarchie, la sélection d'une vue la fera toujours réapparaître, apparemment parce que la visionneuse de hiérarchie oblige la vue sélectionnée à se redessiner.

Cependant, je ne pouvais pas faire fonctionner cela par programme; invalider la vue de liste, ou même le pager de vue entier, n'avait aucun effet.

C'est avec la bibliothèque compatibilité-v4_r7. J'ai également essayé d'utiliser la dernière révision, car elle prétend résoudre de nombreux problèmes liés à l'affichage du pager, mais cela a aggravé les choses (par exemple, les gestes étaient interrompus pour ne plus me permettre de parcourir toutes les pages parfois.)

Quelqu'un d'autre rencontre-t-il également ces problèmes ou avez-vous une idée de ce qui pourrait en être la cause?

Matthias
la source

Réponses:

44

Nous avons finalement réussi à trouver une solution. Apparemment, notre implémentation a souffert de deux problèmes:

  1. notre adaptateur n'a pas supprimé la vue destroyItem().
  2. nous mettions en cache des vues afin que nous devions gonfler notre mise en page une seule fois, et, comme nous ne supprimions pas la vue dans destroyItem(), nous ne l'ajoutions pas instantiateItem()mais renvoyions simplement la vue en cache correspondant à la position actuelle.

Je n'ai pas examiné trop profondément le code source du ViewPager- et ce n'est pas exactement explicite que vous deviez le faire - mais la documentation dit:

destroyItem ()
Supprime une page pour la position donnée. L'adaptateur est responsable de la suppression de la vue de son conteneur, bien qu'il ne doive s'assurer que cela est fait au moment où il revient de finishUpdate (ViewGroup).

et:

Un PagerAdapter très simple peut choisir d'utiliser les vues de page elles-mêmes en tant qu'objets clés, en les renvoyant à partir d'instantiateItem (ViewGroup, int) après la création et en les ajoutant au ViewGroup parent. Une implémentation de destroyItem (ViewGroup, int, Object) correspondante supprimerait la vue du ViewGroup parent et isViewFromObject (View, Object) pourrait être implémentée en tant que return view == object ;.

Donc ma conclusion est que ViewPagers'appuie sur son adaptateur sous-jacent pour ajouter / supprimer explicitement ses enfants dans instantiateItem()/ destroyItem(). Autrement dit, si votre adaptateur est une sous-classe de PagerAdapter, votre sous-classe doit implémenter cette logique.

Remarque: soyez conscient de cela si vous utilisez des listes à l'intérieur ViewPager.

futtetennista
la source
2
J'ai le même problème mais avec FragmentPagerAdapter, qui gère onDestroy pour moi. Aucune des autres solutions ici ne fonctionne non plus.
Greg Ennis
14
Ce qui pourrait également être le problème, c'est que quelqu'un utilise getFragmentManger au lieu de GetChildFragmentManager
Boy
@Boy qu'est-ce que GetChildFragmentManager?
feu dans le trou le
1
@Boy Wooooow .... Je m'étais arraché les cheveux pendant deux jours entiers pour essayer de comprendre pourquoi je ne pouvais rien mettre à jour. MERCI! (ils devraient vraiment, vraiment mettre un avis sur la documentation de Google pour utiliser le childfragmentmanager, ce n'est absolument pas que vous ayez besoin d'un gestionnaire différent).
user0721090601
@futtetennista Je fais de même .. face à ce problème .. pouvez-vous vérifier .. stackoverflow.com/questions/61727835/…
AskQ
55

Si le ViewPagerest défini dans un fragment avec un FragmentPagerAdapter, utilisez getChildFragmentManager()au lieu de getSupportFragmentManager()comme paramètre pour initialiser votre FragmentPagerAdapter.

mAdapter = new MyFragmentPagerAdapter(getChildFragmentManager());

Au lieu de

mAdapter = new MyFragmentPagerAdapter(getSupportFragmentManager());
eyelave
la source
2
Bonne prise, cela semble résoudre mon problème lors de l'utilisation d'un FragmentPagerAdapter
Tobliug
1
Merci - cela a fonctionné. J'essayais de forcer getItem()sur un FragmentPagerAdapterqui était imbriqué dans un fragment recréé.
kosiara - Bartosz Kosarzycki
wow ce travail comme du charme. je suppose que le problème est dû au gonflement du viewpager dans un fragment
Thecarisma
Ses moments comme celui-ci, j'aurais aimé que nous ayons des applaudissements comme Medium afin que je puisse vous donner plus de 1000 applaudissements pour cela. Beau travail, cela devrait être la réponse acceptée
Codelicious
21

J'ai eu exactement le même problème mais j'ai en fait détruit la vue dans destroyItem (j'ai pensé). Le problème était cependant que je l'ai détruit en utilisant viewPager.removeViewAt(index);insted ofviewPager.removeView((View) object);

Faux:

@Override
public void destroyItem(ViewGroup viewPager, int position, Object object) {
    viewPager.removeViewAt(position);
}

Droite:

@Override
public void destroyItem(ViewGroup viewPager, int position, Object object) {
    viewPager.removeView((View) object);
}
Ringen
la source
Merci pour l'indice. Étudie la question depuis un certain temps.
Moritz
2
Cela a fonctionné pour moi, mais savez-vous pourquoi le premier est un problème?
HannahMitt
@HannahMitt removeViewAt supprimera la vue qui se trouve à cette position particulière dans la liste actuelle des enfants du ViewPager, et les pages peuvent être ajoutées au ViewPager dans n'importe quel ordre (la page 0 peut donc être dans le ViewPager à l'index 1 ou autre). removeView parcourra la liste des enfants et supprimera exactement l'objet spécifié.
2
Ne fonctionne pas pour moi: impossible de convertir la vue en fragment. objet ici est un fragment.
FRK
viewpager doit être statique?
Kanagalingam
10

ViewPager essaie de faire des choses intelligentes autour de la réutilisation des éléments, mais il vous oblige à renvoyer de nouvelles positions d'éléments lorsque les choses ont changé. Essayez d'ajouter ceci à votre PagerAdapter:

public int getItemPosition (Object object) { return POSITION_NONE; }

Il indique essentiellement à ViewPager que tout a changé (et le force à tout ré-instancier). C'est la seule chose à laquelle je peux penser du haut de ma tête.

Chris Banes
la source
1
Oui, j'ai lu cette option dans ce fil de discussion: stackoverflow.com/questions/7263291/… - cependant, cela semble être l'approche du marteau. Il doit sûrement y avoir une manière plus élégante? Je me demande si cela est lié à l'utilisation de listes comme pages de pagination?
Matthias
C'est un peu une approche de masse, mais vous pouvez revenir en arrière. c'est-à-dire renvoyer une valeur correcte de la méthode.
Chris Banes
@ChrisBanes Comme vous l'avez dit appeler return POSITION_NONE;, forces it to re-instantiate everythingmais dans mon cas, je ne veux pas tout ré-instancier , alors serait-il possible de supprimer viewsans effacer les éléments existants? S'il vous plaît laissez-moi savoir
Ritesh Adulkar
vous êtes la saveur de la vie
masque8
2

J'ai essayé trop de solutions mais a viewPager.post()fonctionné de manière inattendue

 mAdapter = new NewsVPAdapter(getContext(), articles);
    viewPager.post(new Runnable() {
        @Override
        public void run() {
            viewPager.setAdapter(mAdapter);
        }
    });
Zeero0
la source
1
mon Dieu. pourquoi personne n'a voté pour cela? J'ai eu un comportement étrange lorsque je ressaisis un fragment et que le visualiseur à l'intérieur ne se rendait pas lui-même, et le retarder un peu comme ça a en fait résolu le problème!
Fugogugo
0

La bibliothèque de support Android a une activité de démonstration qui comprend un ViewPager avec un ListView sur chaque page. Vous devriez probablement jeter un œil et voir ce qu'il fait.

Dans Eclipse (avec Android Dev Tools r20):

  1. Sélectionner New > Android Sample Project
  2. Sélectionnez votre niveau d'API cible (je suggère le plus récent disponible)
  3. Sélectionner Support4Demos
  4. Cliquez avec le bouton droit sur le projet et sélectionnez Android Tools > Add Support Library
  5. Exécutez l'application et sélectionnez Fragment, puisPager

Le code pour cela est au format src/com.example.android.supportv4.app/FragmentPagerSupport.java. Bonne chance!

Sparky
la source
merci - je vais jeter un oeil à ça! Je vais peut-être repérer quelque chose que nous nous trompons.
Matthias
0

J'ai rencontré cela et j'ai eu des problèmes très similaires. Je l'ai même demandé sur le débordement de pile.

Pour moi, dans le parent du parent de ma vue, quelqu'un a sous LinearLayout-classé et remplacé requestLayout()sans appeler super.requestLayout(). Cela a empêché onMeasureet onLayoutd'être appelé sur mon ViewPager (bien que hierarchyviewer les appelle manuellement). Sans être mesurés, ils s'afficheront comme vides dans ViewPager.

Vérifiez donc vos vues contenant. Assurez-vous qu'ils sous-classe de View et ne remplacent pas aveuglément requestLayout ou quelque chose de similaire.

Tim O'Brien
la source
0

Eu le même problème, qui est quelque chose à voir avec ListView(parce que ma vue vide apparaît bien si la liste est vide). Je viens d'appeler requestLayout()la problématique ListView. Maintenant ça dessine bien!

Oleg Vaskevich
la source
0

J'ai rencontré ce même problème lors de l'utilisation d'un ViewPager et d'un FragmentStatePagerAdapter. J'ai essayé d'utiliser un gestionnaire avec un délai de 3 secondes pour appeler invalidate () et requestLayout () mais cela n'a pas fonctionné. Ce qui a fonctionné a été de réinitialiser la couleur d'arrière-plan de viewPager comme suit:

MyFragment.java

    private Handler mHandler;
    private Runnable mBugUpdater;

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {
        View rootView = new ViewPager(getActivity());
        //...Create your adapter and set it here...

        mHandler = new Handler();
        mBugUpdater = new Runnable(){
            @Override
            public void run() {
                mVp.setBackgroundColor(mItem.getBackgroundColor());
                mHandler = null;
                mBugUpdater = null;
            }           
        };
        mHandler.postDelayed(mBugUpdater,50);

        return rootView;
    }

    @Override
    public void onPause() {
        if(mHandler != null){
            //Remove the callback if it hasn't triggered yet
            mHandler.removeCallbacks(mBugUpdater);
            mHandler = null;
            mBugUpdater = null;
        }
        super.onPause();
     }
Chris Sprague
la source
0

J'ai eu un problème avec les mêmes symptômes, mais une cause différente qui s'est avérée être une erreur stupide de ma part. Je pensais l'ajouter ici au cas où cela aiderait quelqu'un.

J'avais un ViewPager utilisant FragmentStatePagerAdapter qui avait auparavant deux fragments, mais j'en ai ajouté plus tard un troisième. Cependant, j'ai oublié que la limite de page par défaut hors écran est de 1 - donc, lorsque je passerais au nouveau troisième fragment, le premier serait détruit, puis recréé après le retour. Le problème était que mon activité était en charge de notifier ces fragments pour initialiser leur état d'interface utilisateur. Cela s'est produit lorsque l'activité et les cycles de vie des fragments étaient les mêmes, mais pour y remédier, j'ai dû modifier les fragments pour initialiser leur propre interface utilisateur au cours de leur cycle de vie de démarrage. En fin de compte, j'ai également fini par changer setOffscreenPageLimit à 2 afin que les trois fragments soient conservés en vie à tout moment (sans danger dans ce cas car ils n'étaient pas très gourmands en mémoire).

dfinn
la source
-1

J'ai eu un problème similaire. Je mets en cache des vues parce que je n'ai besoin que de 3 vues ViewPager. Lorsque je glisse vers l'avant, tout va bien, mais lorsque je commence à glisser vers l'arrière, une erreur se produit, cela dit que "ma vue a déjà un parent". La solution consiste à supprimer manuellement les éléments inutiles.

@Override
    public Object instantiateItem(ViewGroup container, int position) {
        int localPos = position % SIZE;
        TouchImageView view;
        if (touchImageViews[localPos] != null) {
            view = touchImageViews[localPos];
        } else {
            view = new TouchImageView(container.getContext());
            view.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
            touchImageViews[localPos] = view;
        }
        view.setImageDrawable(mDataModel.getPhoto(position));
        Log.i(IRViewPagerAdpt.class.toString(), "Add view " + view.toString() + " at pos: " + position + " " + localPos);
        if (view.getParent() == null) {
        ((ViewPager) container).addView(view);
    }
        return view;
    }

    @Override
    public void destroyItem(ViewGroup container, int position, Object view) {
        //      ((ViewPager) container).removeView((View) view);
        Log.i(IRViewPagerAdpt.class.toString(), "remove view " + view.toString() + " at pos: " + position);
    }

..................

private static final int SIZE = 3;
private TouchImageView[] touchImageViews = new TouchImageView[SIZE];
Roman Nazarevych
la source
-1

Pour moi, le problème revenait à l'activité après l'arrêt du processus d'application. J'utilise un adaptateur de pager de vue personnalisé modifié à partir des sources Android. Le pageur de vue est intégré directement dans l'activité.

Appel viewPager.setCurrentItem(position, true);

(avec animation) après avoir défini les données et notifyDataSetChanged () semble fonctionner, mais si le paramètre est défini sur false, ce n'est pas le cas et le fragment est vide. C'est un cas de pointe qui peut être utile à quelqu'un.

Méchant homme
la source