ViewPager PagerAdapter ne met pas à jour la vue


597

J'utilise ViewPager de la bibliothèque de compatibilité. Je l'ai réussi à afficher plusieurs vues que je peux parcourir.

Cependant, j'ai du mal à trouver comment mettre à jour le ViewPager avec un nouvel ensemble de vues.

J'ai essayé toutes sortes de choses comme appeler mAdapter.notifyDataSetChanged(), mViewPager.invalidate()même créer un nouvel adaptateur chaque fois que je veux utiliser une nouvelle liste de données.

Rien n'a aidé, les vues de texte restent inchangées par rapport aux données d'origine.

Mise à jour: j'ai fait un petit projet de test et j'ai presque pu mettre à jour les vues. Je vais coller la classe ci-dessous.

Ce qui ne semble pas être mis à jour est cependant la 2ème vue, le «B» reste, il devrait afficher «Y» après avoir appuyé sur le bouton de mise à jour.

public class ViewPagerBugActivity extends Activity {

    private ViewPager myViewPager;
    private List<String> data;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        data = new ArrayList<String>();
        data.add("A");
        data.add("B");
        data.add("C");

        myViewPager = (ViewPager) findViewById(R.id.my_view_pager);
        myViewPager.setAdapter(new MyViewPagerAdapter(this, data));

        Button updateButton = (Button) findViewById(R.id.update_button);
        updateButton.setOnClickListener(new OnClickListener() {

            @Override
            public void onClick(View v) {
                updateViewPager();
            }
        });
    }

    private void updateViewPager() {
        data.clear();
        data.add("X");
        data.add("Y");
        data.add("Z");
        myViewPager.getAdapter().notifyDataSetChanged();
    }

    private class MyViewPagerAdapter extends PagerAdapter {

        private List<String> data;
        private Context ctx;

        public MyViewPagerAdapter(Context ctx, List<String> data) {
            this.ctx = ctx;
            this.data = data;
        }

        @Override
        public int getCount() {
            return data.size();
        }

        @Override
        public Object instantiateItem(View collection, int position) {
            TextView view = new TextView(ctx);
            view.setText(data.get(position));
            ((ViewPager)collection).addView(view);
            return view;
        }

        @Override
        public void destroyItem(View collection, int position, Object view) {
             ((ViewPager) collection).removeView((View) view);
        }

        @Override
        public boolean isViewFromObject(View view, Object object) {
            return view == object;
        }

        @Override
        public Parcelable saveState() {
            return null;
        }

        @Override
        public void restoreState(Parcelable arg0, ClassLoader arg1) {
        }

        @Override
        public void startUpdate(View arg0) {
        }

        @Override
        public void finishUpdate(View arg0) {
        }
    }
}

1
Veuillez consulter stackoverflow.com/questions/12510404/… pour un bogue potentiel avec FragmentStatePagerAdapter.
samis

2
Désolé de draguer ça. Votre question m'a vraiment aidé alors merci! La seule chose que je n'obtiens pas est de savoir comment la référence aux données dans le PagerAdapter est modifiée lorsque vous la mettez à jour dans l'activité
Ewanw

Faites étendre votre adaptateur BaseAdapter Reportez-vous à: stackoverflow.com/questions/13664155/…

1
Apparemment, si vous redéfinissez l'adaptateur sur votre pager de vue, il se réinitialise.
EpicPandaForce

j'ai un problème dans le pager stackoverflow.com/questions/41217237/…
chris

Réponses:


856

Il existe plusieurs façons d'y parvenir.

La première option est plus facile, mais un peu plus inefficace.

Remplacez getItemPositionvotre PagerAdaptercomme ceci:

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

De cette façon, lorsque vous appelez notifyDataSetChanged(), le téléavertisseur de vue supprimera toutes les vues et les rechargera toutes. Ainsi, l'effet de rechargement est obtenu.

La deuxième option, suggérée par Alvaro Luis Bustamante (anciennement alvarolb) , est de setTag()procédé instantiateItem()lors de l' instanciation d' une nouvelle vue. Ensuite, au lieu d'utiliser notifyDataSetChanged(), vous pouvez utiliser findViewWithTag()pour trouver la vue que vous souhaitez mettre à jour.

La deuxième approche est très flexible et très performante. Félicitations à alvarolb pour la recherche originale.


4
Mieux que mon travail, ne semble avoir aucun effet secondaire, merci.
C0deAttack

3
J'ai le même problème. Mon viewPager affichera les nouveaux résultats de recherche, mais ne revient pas par défaut à la première entrée du curseur. Quelqu'un sait-il comment réinitialiser la position lors de la mise à jour de l'ensemble de données?
mAndroid

1
@mAndroid, vous devez déposer une question séparée avec plus de détails.
rui.araujo

1
renvoyer POSITION_NONE ajoute apparemment beaucoup d'inefficacité à l'application globale, car cela entraîne la suppression par Android de la vue associée à la position interrogée. La vue doit ensuite être recréée à nouveau avant d'être affichée. Résout le problème des vues non mises à jour, mais probablement pas la meilleure solution pour les mises en page complexes.
wufoo

11
Meilleure réponse (sans supprimer tous les éléments) -> stackoverflow.com/a/10852046/898056
yhpark

484

Je ne pense pas qu'il y ait de bug dans le PagerAdapter. Le problème est que comprendre comment cela fonctionne est un peu complexe. En regardant les solutions expliquées ici, il y a un malentendu et donc une mauvaise utilisation des vues instanciées de mon point de vue.

Les derniers jours avec lesquels j'ai travaillé PagerAdapteret ViewPager, et j'ai trouvé ce qui suit:

La notifyDataSetChanged()méthode sur le PagerAdapternotifiera uniquement ViewPagerque les pages sous-jacentes ont changé. Par exemple, si vous avez créé / supprimé des pages de façon dynamique (en ajoutant ou en supprimant des éléments de votre liste), le ViewPagerdevrait s'en occuper. Dans ce cas, je pense que le ViewPagerdétermine si une nouvelle vue doit être supprimée ou instanciée à l'aide des méthodes getItemPosition()et getCount().

Je pense que ViewPager, après un notifyDataSetChanged()appel prend ses vues des enfants et vérifie leur position avec le getItemPosition(). Si pour une vue enfant, cette méthode revient POSITION_NONE, le ViewPagercomprend que la vue a été supprimée, en appelant destroyItem()et en supprimant cette vue.

De cette façon, remplacer getItemPosition()pour toujours revenir POSITION_NONEest complètement faux si vous ne souhaitez mettre à jour que le contenu des pages, car les vues précédemment créées seront détruites et de nouvelles seront créées à chaque appel notifyDatasetChanged(). Cela peut sembler ne pas être si mal pour quelques TextViewsecondes, mais lorsque vous avez des vues complexes, comme ListViews remplies à partir d'une base de données, cela peut être un vrai problème et un gaspillage de ressources.

Il existe donc plusieurs approches pour modifier efficacement le contenu d'une vue sans avoir à supprimer et instancier à nouveau la vue. Cela dépend du problème que vous souhaitez résoudre. Mon approche consiste à utiliser la setTag()méthode pour toute vue instanciée dans la instantiateItem()méthode. Ainsi, lorsque vous souhaitez modifier les données ou invalider la vue dont vous avez besoin, vous pouvez appeler la findViewWithTag()méthode sur le ViewPagerpour récupérer la vue précédemment instanciée et la modifier / l'utiliser comme vous le souhaitez sans avoir à supprimer / créer une nouvelle vue à chaque fois que vous le souhaitez. pour mettre à jour une valeur.

Imaginez par exemple que vous avez 100 pages avec 100 TextViews et que vous ne souhaitez mettre à jour qu'une seule valeur périodiquement. Avec les approches expliquées précédemment, cela signifie que vous supprimez et instanciez 100 TextViews à chaque mise à jour. Cela n'a aucun sens...


11
+1 - Votre solution fonctionne non seulement comme un charme, j'ai rencontré exactement le problème que vous avez décrit ici lorsque j'ai dû mettre à jour des pages contenant des onglets avec des vues de liste, des vues de texte et des images. (OutOfErrorExceptions et autres ...) Merci!
schlingel

3
@alvarolb: Bonjour, je ne sais pas vraiment ce que vous voulez dire setTagdans la méthode onInstantiateItem, souhaitez-vous mettre à jour cette réponse avec du codage? Merci.
Huy Tower

35
Ce serait parfait si vous écriviez un exemple.
Sami Eltamawy

13
Comme le dit quelqu'un: un exemple serait apprécié!
Jey10

15
6 ans maintenant, personne ne peut donner d'exemple.
Oussaki

80

Changez FragmentPagerAdapterpour FragmentStatePagerAdapter.

Remplacez la getItemPosition()méthode et retournez POSITION_NONE.

Finalement, il écoutera notifyDataSetChanged()le téléavertisseur à l' écran.


Je pense que vous êtes censé implémenter correctement l'ensemble getItemPosition ET retourner POSITION_NONE si le fragment n'est pas trouvé.
tkhduracell

46

La réponse donnée par alvarolb est certainement la meilleure façon de le faire. En s'appuyant sur sa réponse, un moyen simple de mettre en œuvre cela consiste simplement à stocker les vues actives par position:

SparseArray<View> views = new SparseArray<View>();

@Override
public Object instantiateItem(View container, int position) {
    View root = <build your view here>;
    ((ViewPager) container).addView(root);
    views.put(position, root);
    return root;
}

@Override
public void destroyItem(View collection, int position, Object o) {
    View view = (View)o;
    ((ViewPager) collection).removeView(view);
    views.remove(position);
    view = null;
}

Puis une fois en surchargeant la notifyDataSetChangedméthode, vous pouvez rafraîchir les vues ...

@Override
public void notifyDataSetChanged() {
    int key = 0;
    for(int i = 0; i < views.size(); i++) {
       key = views.keyAt(i);
       View view = views.get(key);
       <refresh view with new data>
    }
    super.notifyDataSetChanged();
}

Vous pouvez réellement utiliser un code similaire dans instantiateItemet notifyDataSetChangedpour actualiser votre vue. Dans mon code, j'utilise exactement la même méthode.


8
pouvez-vous fournir un exemple d'adaptateur complet? est-ce que cela se fait en utilisant des fragments ou non?

9
<rafraîchir la vue avec de nouvelles données> ?? qu'est-ce que cela signifie, soit nous devons ajouter la vue ici, soit rien?
Akarsh M

Cette méthode met à jour toutes les vues. Si vous ne souhaitez mettre à jour qu'une seule vue, vous pouvez écrire votre propre méthode notifyDataItemChanged comme ceci: public void notifyDataItemChanged (int pos) {TextView tv = (TextView) views.get (pos) .findViewById (R.id.tv); tv.setText (list.get (pos)); super.notifyDataSetChanged (); }
Kai Wang

Ce code se bloque si la nouvelle taille du téléavertisseur est inférieure à l'ancienne
Anton Duzenko

@AntonDuzenko correct, la suppression des vues est assez difficile.
MLProgrammer-CiM

28

Eu le même problème. Pour moi, cela a fonctionné pour étendre FragmentStatePagerAdapter et remplacer les méthodes ci-dessous:

@Override
public Parcelable saveState() {
    return null;
}

@Override
public void restoreState(Parcelable state, ClassLoader loader) {

}

C'est aussi la seule solution qui a fonctionné pour moi. Mon cas ne met pas à jour le fragment, mais supprime ou ajoute dynamiquement des fragments. Sans étendre FragmentStatePagerAdapter, ViewPager renvoie des fragments mis en cache, ce qui est une position incorrecte. Merci!
Sira Lam

Fonctionne bien sur la bibliothèque de support 28.0.0
Oleksii K.

Remplacer pour faire quoi? Comment créer un objet d'état associé à un adaptateur?
Phani Rithvij

Merci mon pote. Cette solution ne fonctionne que
Parthi

20

Après des heures de frustration en essayant toutes les solutions ci-dessus pour surmonter ce problème et en essayant également de nombreuses solutions sur d'autres questions similaires comme celle-ci , ceci et cela qui ont échoué avec moi pour résoudre ce problème et pour faire ViewPagerdétruire l'ancien Fragmentet remplir le pageravec le nouveau par Fragment. J'ai résolu le problème comme suit:

1) Faites en sorte que la ViewPagerclasse s'étende FragmentPagerAdaptercomme suit:

 public class myPagerAdapter extends FragmentPagerAdapter {

2) Créez un article pour le ViewPagerqui stocke le titleet le fragmentsuivant:

public class PagerItem {
private String mTitle;
private Fragment mFragment;


public PagerItem(String mTitle, Fragment mFragment) {
    this.mTitle = mTitle;
    this.mFragment = mFragment;
}
public String getTitle() {
    return mTitle;
}
public Fragment getFragment() {
    return mFragment;
}
public void setTitle(String mTitle) {
    this.mTitle = mTitle;
}

public void setFragment(Fragment mFragment) {
    this.mFragment = mFragment;
}

}

3) Faites en ViewPagersorte que le constructeur de la prise mon FragmentManagerinstance le stocke dans mon classcomme suit:

private FragmentManager mFragmentManager;
private ArrayList<PagerItem> mPagerItems;

public MyPagerAdapter(FragmentManager fragmentManager, ArrayList<PagerItem> pagerItems) {
    super(fragmentManager);
    mFragmentManager = fragmentManager;
    mPagerItems = pagerItems;
}

4) Créer une méthode pour définir les re- adapterdonnées avec les nouvelles données en supprimant toutes les précédentes fragmentdu fragmentManagermême directement pour la adapterpour définir la nouvelle fragmentde la nouvelle liste à nouveau comme suit:

public void setPagerItems(ArrayList<PagerItem> pagerItems) {
    if (mPagerItems != null)
        for (int i = 0; i < mPagerItems.size(); i++) {
            mFragmentManager.beginTransaction().remove(mPagerItems.get(i).getFragment()).commit();
        }
    mPagerItems = pagerItems;
}

5) À partir du conteneur Activityou Fragmentne réinitialisez pas l'adaptateur avec les nouvelles données. Définissez les nouvelles données via la méthode setPagerItemsavec les nouvelles données comme suit:

ArrayList<PagerItem> pagerItems = new ArrayList<PagerItem>();
pagerItems.add(new PagerItem("Fragment1", new MyFragment1()));
pagerItems.add(new PagerItem("Fragment2", new MyFragment2()));

mPagerAdapter.setPagerItems(pagerItems);
mPagerAdapter.notifyDataSetChanged();

J'espère que ça aide.


@Tomer quel était le problème auquel vous avez été confronté?
Sami Eltamawy


9

J'ai eu le même problème et ma solution utilise FragmentPagerAdapteren remplaçant FragmentPagerAdapter#getItemId(int position):

@Override
public long getItemId(int position) {
    return mPages.get(position).getId();
}

Par défaut, cette méthode renvoie la position de l'élément. Je suppose que ViewPagervérifie si a itemIdété modifié et recrée la page uniquement si elle l'est. Mais la version itemIdnon remplacée renvoie la même position que même si la page est réellement différente, et ViewPager ne définit pas que la page est remplacée et doit être recréée.

Pour l'utiliser, long id est nécessaire pour chaque page. Normalement, il devrait être unique, mais je suggère, dans ce cas, qu'il devrait simplement être différent de la valeur précédente pour la même page. Ainsi, il est possible d'utiliser ici un compteur continu dans l'adaptateur ou des entiers aléatoires (avec une large distribution).

Je pense que c'est une manière plus cohérente d'utiliser plutôt les balises de vue mentionnées comme solution dans ce sujet. Mais probablement pas pour tous les cas.


1
C'est. Je l'ai vérifié: juste POSITION_NONE ne suffit pas. Merci pour votre merveilleuse réponse! :)
Slava

Cette fonction n'est même pas présente dans PagerAdapter . De quelle classe s'agit-il?
Saket

@Saket, vous avez raison, la classe estFragmentPagerAdapter
atlascoder

6

J'ai trouvé une décision très intéressante de ce problème. Au lieu d'utiliser FragmentPagerAdapter , qui garde en mémoire tous les fragments, nous pouvons utiliser FragmentStatePagerAdapter ( android.support.v4.app.FragmentStatePagerAdapter ), qui recharge le fragment à chaque fois, lorsque nous le sélectionnons.

Les réalisations des deux adaptateurs sont identiques. Donc, nous avons juste besoin de changer " étendre FragmentPagerAdapter " sur " étendre FragmentStatePagerAdapter "


bon point, cela aidera d'autres à résoudre les problèmes de mémoire de viewPager liés au fragment
Ashu Kumar

Excellente réponse, cela m'a beaucoup aidé.
Sergio Marani

5

Après beaucoup de recherches sur ce problème, j'ai trouvé une très bonne solution qui, selon moi, est la bonne façon de procéder. Essentiellement, instantiateItem n'est appelé que lorsque la vue est instanciée et plus jamais sauf si la vue est détruite (c'est ce qui se produit lorsque vous remplacez la fonction getItemPosition pour renvoyer POSITION_NONE). Au lieu de cela, vous voulez enregistrer les vues créées et de les mettre à jour dans l'adaptateur, de générer une fonction get pour que quelqu'un d'autre puisse la mettre à jour ou une fonction set qui met à jour l'adaptateur (mon préféré).

Donc, dans votre MyViewPagerAdapter ajoutez une variable comme:

private View updatableView;

un dans votre instantiateItem:

 public Object instantiateItem(View collection, int position) {
        updatableView = new TextView(ctx); //My change is here
        view.setText(data.get(position));
        ((ViewPager)collection).addView(view);
        return view;
    }

Ainsi, de cette façon, vous pouvez créer une fonction qui mettra à jour votre vue:

public updateText(String txt)
{
    ((TextView)updatableView).setText(txt);
}

J'espère que cela t'aides!


J'ai réalisé que je ne définissais pas les données, instantiateItemc'est pourquoi mes vues ne se mettaient pas à jour. D'après votre réponse, je l'ai réalisé. +1
Phani Rithvij

5

Deux ans et demi après que le PO a posé sa question, cette question est toujours, eh bien, toujours un problème. Il est évident que la priorité de Google à ce sujet n'est pas particulièrement élevée, donc plutôt que de trouver un correctif, j'ai trouvé une solution de contournement. La grande percée pour moi a été de découvrir quelle était la véritable cause du problème (voir la réponse acceptée dans cet article ). Une fois qu'il était évident que le problème était que les pages actives n'étaient pas correctement actualisées, ma solution de contournement était évidente:

Dans mon fragment (les pages):

  • J'ai pris tout le code qui remplit le formulaire de onCreateView et l'ai mis dans une fonction appelée PopulateForm qui peut être appelée de n'importe où, plutôt que par le cadre. Cette fonction tente d'obtenir la vue actuelle à l'aide de getView, et si elle est nulle, elle revient simplement. Il est important que PopulateForm ne contienne que le code qui s'affiche - tous les autres codes qui créent des écouteurs FocusChange et similaires sont toujours dans OnCreate
  • Créez un booléen qui peut être utilisé comme indicateur indiquant que le formulaire doit être rechargé. Le mien est mbReloadForm
  • Substituez OnResume () pour appeler PopulateForm () si mbReloadForm est défini.

Dans mon activité, où je fais le chargement des pages:

  • Allez à la page 0 avant de changer quoi que ce soit. J'utilise FragmentStatePagerAdapter, donc je sais que deux ou trois pages sont affectées au maximum. Passer à la page 0 garantit que je n'ai que le problème sur les pages 0, 1 et 2.
  • Avant d'effacer l'ancienne liste, prenez sa taille (). De cette façon, vous savez combien de pages sont affectées par le bogue. Si> 3, réduisez-le à 3 - si vous utilisez un PagerAdapter différent, vous devrez voir combien de pages vous devez traiter (peut-être toutes?)
  • Rechargez les données et appelez pageAdapter.notifyDataSetChanged ()
  • Maintenant, pour chacune des pages affectées, voyez si la page est active en utilisant pager.getChildAt (i) - cela vous indique si vous avez une vue. Si tel est le cas, appelez pager.PopulateView (). Sinon, définissez l'indicateur ReloadForm.

Après cela, lorsque vous rechargez un deuxième ensemble de pages, le bogue entraînera toujours l'affichage des anciennes données par certaines. Cependant, ils seront maintenant actualisés et vous verrez les nouvelles données - vos utilisateurs ne sauront pas que la page était jamais incorrecte car cette actualisation se produira avant de voir la page.

J'espère que cela aide quelqu'un!


5

Toutes ces solutions ne m'ont pas aidé. j'ai donc trouvé une solution de travail: vous pouvez à setAdapterchaque fois, mais ce n'est pas suffisant. vous devez le faire avant de changer d'adaptateur:

FragmentManager fragmentManager = slideShowPagerAdapter.getFragmentManager();
FragmentTransaction transaction = fragmentManager.beginTransaction();
List<Fragment> fragments = fragmentManager.getFragments();
for (Fragment f : fragments) {
    transaction.remove(f);
}
transaction.commit();

et après cela:

viewPager.setAdapter(adapter);

Merci! Dans mon cas, j'ai utilisé un ViewPagerfragment intérieur, donc remplacé slideShowPagerAdapter.getFragmentManager()par getChildFragmentManager(). Peut getFragmentManager()- être vous aidera dans votre cas. J'ai utilisé FragmentPagerAdapter, non FragmentStatePagerAdapter. Voir également stackoverflow.com/a/25994654/2914140 pour une méthode hacky.
CoolMind

5

Une manière beaucoup plus simple: utilisez un FragmentPagerAdapter, et enveloppez vos vues paginées sur des fragments. Ils sont mis à jour


5

Merci rui.araujo et Alvaro Luis Bustamante. Au début, j'essaie d'utiliser la voie de rui.araujo, car c'est facile. Cela fonctionne mais lorsque les données changent, la page se redessine évidemment. C'est mauvais alors j'essaie d'utiliser la voie d'Alvaro Luis Bustamante. C'est parfait. Voici le code:

@Override
protected void onStart() {
    super.onStart();
}

private class TabPagerAdapter extends PagerAdapter {
    @Override
    public int getCount() {
        return 4;
    }

    @Override
    public boolean isViewFromObject(final View view, final Object object) {
        return view.equals(object);
    }

    @Override
    public void destroyItem(final View container, final int position, final Object object) {
        ((ViewPager) container).removeView((View) object);
    }

    @Override
    public Object instantiateItem(final ViewGroup container, final int position) {
        final View view = LayoutInflater.from(
                getBaseContext()).inflate(R.layout.activity_approval, null, false);
        container.addView(view);
        ListView listView = (ListView) view.findViewById(R.id.list_view);
        view.setTag(position);
        new ShowContentListTask(listView, position).execute();
        return view;
    }
}

Et quand les données changent:

for (int i = 0; i < 4; i++) {
    View view = contentViewPager.findViewWithTag(i);
    if (view != null) {
        ListView listView = (ListView) view.findViewById(R.id.list_view);
        new ShowContentListTask(listView, i).execute();
    }
}

4

Juste au cas où quelqu'un utiliserait l' adaptateur basé sur FragmentStatePagerAdapter (qui permettra à ViewPager de créer les pages minimales nécessaires à l'affichage, au plus 2 pour mon cas), la réponse de @ rui.araujo d'écraser getItemPosition dans votre adaptateur ne causera pas de gaspillage important, mais cela continuera peut être amélioré.

En pseudo code:

public int getItemPosition(Object object) {
    YourFragment f = (YourFragment) object;
    YourData d = f.data;
    logger.info("validate item position on page index: " + d.pageNo);

    int dataObjIdx = this.dataPages.indexOf(d);

    if (dataObjIdx < 0 || dataObjIdx != d.pageNo) {
        logger.info("data changed, discard this fragment.");
        return POSITION_NONE;
    }

    return POSITION_UNCHANGED;
}

dans ViewPager, il était assez facile de connaître la position passée de l'élément, l'implémentation la plus parfaite est simplement de renvoyer la nouvelle position getItemPosition()lorsque le jeu de données a changé, et ViewPager saura qu'ils sont modifiés ou non. malheureusement, ViewPager ne l'a pas fait.
VinceStyling

3

J'ai eu un problème similaire dans lequel j'avais quatre pages et l'une des pages a mis à jour les vues sur les trois autres. J'ai pu mettre à jour les widgets (SeekBars, TextViews, etc.) sur la page adjacente à la page actuelle. Les deux dernières pages auraient des widgets non initialisés lors de l'appel mTabsAdapter.getItem(position).

Pour résoudre mon problème, j'ai utilisé setSelectedPage(index)avant d'appelergetItem(position) . Cela instancierait la page, me permettant de pouvoir modifier les valeurs et les widgets sur chaque page.

Après toutes les mises à jour que j'utiliserais, setSelectedPage(position)suivies denotifyDataSetChanged() .

Vous pouvez voir un léger scintillement dans le ListView sur la page de mise à jour principale, mais rien de notable. Je ne l'ai pas testé complètement, mais cela résout mon problème immédiat.


Salut, je suis intéressé par votre code, je fais face à un problème similaire, je suis capable de mettre à jour les fragments adjacents mais le fragment actuel qui est visible n'est pas mis à jour. Je suis très confus avec toutes les réponses ici. comment puis-je mettre à jour les vues dans le fragment actuel qui est montré par l'adaptateur VIewPager
Pankaj Nimgade

3

Je poste juste cette réponse au cas où quelqu'un d'autre la trouverait utile. Pour faire exactement la même chose, j'ai simplement pris le code source de ViewPager et PagerAdapter de la bibliothèque de compatibilité et je l'ai compilé dans mon code (vous devez trier toutes les erreurs et les importer vous-même, mais cela peut certainement être fait).

Ensuite, dans le CustomViewPager, créez une méthode appelée updateViewAt (int position). La vue elle-même peut être obtenue à partir des objets ArrayList définis dans la classe ViewPager (vous devez définir un identifiant pour les vues à l'élément instancier et comparer cet identifiant avec la position dans la méthode updateViewAt ()). Ensuite, vous pouvez mettre à jour la vue si nécessaire.


2

Je suppose que j'ai la logique de ViewPager.

Si j'ai besoin de rafraîchir un ensemble de pages et de les afficher en fonction du nouvel ensemble de données, j'appelle notifyDataSetChanged () . Ensuite, ViewPager effectue un certain nombre d'appels à getItemPosition () , en y passant Fragment en tant qu'objet . Ce fragment peut provenir d'un ancien ensemble de données (que je souhaite supprimer) ou d'un nouveau (que je souhaite afficher). Donc, je remplace getItemPosition () et là je dois déterminer en quelque sorte si mon fragment provient de l'ancien ensemble de données ou du nouveau.

Dans mon cas, j'ai une disposition à 2 volets avec une liste des éléments supérieurs dans le volet gauche et une vue de balayage (ViewPager) à droite. Donc, je stocke un lien vers mon élément supérieur actuel dans mon PagerAdapter et également à l'intérieur de chaque fragment de page instancié. Lorsque l'élément supérieur sélectionné dans la liste change, je stocke le nouvel élément supérieur dans PagerAdapter et j'appelle notifyDataSetChanged () . Et dans la getItemPosition () substituée, je compare l'élément supérieur de mon adaptateur à l'élément supérieur de mon fragment. Et seulement si elles ne sont pas égales, je renvoie POSITION_NONE. Ensuite, PagerAdapter rétablit tous les fragments qui ont renvoyé POSITION_NONE.

REMARQUE. Le stockage de l'ID de l'élément supérieur au lieu d'une référence peut être une meilleure idée.

L'extrait de code ci-dessous est un peu schématique, mais je l'ai adapté du code qui fonctionne réellement.

public class SomeFragment extends Fragment {
  private TopItem topItem;
}

public class SomePagerAdapter extends FragmentStatePagerAdapter {
  private TopItem topItem;

  public void changeTopItem(TopItem newTopItem) {
    topItem = newTopItem;
    notifyDataSetChanged();
  }

  @Override
  public int getItemPosition(Object object) {
    if (((SomeFragment) object).getTopItemId() != topItem.getId()) {
      return POSITION_NONE;
    }
    return super.getItemPosition(object);
  }
}

Merci à tous les chercheurs précédents!


2

Le code ci-dessous a fonctionné pour moi.

Créez une classe qui étend la classe FragmentPagerAdapter comme ci-dessous.

public class Adapter extends FragmentPagerAdapter {

private int tabCount;
private Activity mActivity;
private Map<Integer, String> mFragmentTags;
private FragmentManager mFragmentManager;
private int container_id;
private ViewGroup container;
private List<Object> object;

public Adapter(FragmentManager fm) {
    super(fm);
}

public Adapter(FragmentManager fm, int numberOfTabs , Activity mA) {
    super(fm);
    mActivity = mA;
    mFragmentManager = fm;
    object = new ArrayList<>();
    mFragmentTags = new HashMap<Integer, String>();
    this.tabCount = numberOfTabs;
}

@Override
public Fragment getItem(int position) {
    switch (position) {
        case 0:
            return Fragment0.newInstance(mActivity);
        case 1:
            return Fragment1.newInstance(mActivity);
        case 2:
            return Fragment2.newInstance(mActivity);
        default:
            return null;
    }}


@Override
public Object instantiateItem(ViewGroup container, int position) {
    Object object = super.instantiateItem(container, position);
    if (object instanceof Fragment) {
        Log.e("Already defined","Yes");
        Fragment fragment = (Fragment) object;
        String tag = fragment.getTag();
        Log.e("Fragment Tag","" + position + ", " + tag);
        mFragmentTags.put(position, tag);
    }else{
        Log.e("Already defined","No");
    }
    container_id = container.getId();
    this.container = container;
    if(position == 0){
        this.object.add(0,object);
    }else if(position == 1){
        this.object.add(1,object);
    }else if(position == 2){
        this.object.add(2,object);
    }
    return object;
}

@Override
public void destroyItem(ViewGroup container, int position, Object object) {
    super.destroyItem(container, position, object);
    if (object instanceof Fragment) {
        Log.e("Removed" , String.valueOf(position));
    }
}

@Override
public int getItemPosition (Object object)
{   int index = 0;
    if(this.object.get(0) == object){
        index = 0;
    }else if(this.object.get(1) == object){
        index = 1;
    }else if(this.object.get(2) == object){
        index = 2;
    }else{
        index = -1;
    }
    Log.e("Index" , "..................." + String.valueOf(index));
    if (index == -1)
        return POSITION_NONE;
    else
        return index;
}

public String getFragmentTag(int pos){
    return "android:switcher:"+R.id.pager+":"+pos;
}

public void NotifyDataChange(){
    this.notifyDataSetChanged();
}

public int getcontainerId(){
    return container_id;
}

public ViewGroup getContainer(){
    return this.container;
}

public List<Object> getObject(){
    return this.object;
}

@Override
public int getCount() {
    return tabCount;
}}

Ensuite, à l'intérieur de chaque fragment que vous avez créé, créez une méthode updateFragment. Dans cette méthode, vous modifiez les éléments à modifier dans le fragment. Par exemple, dans mon cas, Fragment0 contenait un GLSurfaceView qui affiche un objet 3D basé sur un chemin vers un fichier .ply, donc à l'intérieur de ma méthode updateFragment je change le chemin vers ce fichier ply.

puis créez une instance de ViewPager,

viewPager = (ViewPager) findViewById(R.id.pager);

et une instance Adpater,

adapter = new Adapter(getSupportFragmentManager(), 3, this);

alors fais ça,

viewPager.setAdapter(adapter);
viewPager.setOffscreenPageLimit(1);

Ensuite, à l'intérieur de la classe, vous avez initialisé la classe Adapter ci-dessus et créé un viewPager, chaque fois que vous souhaitez mettre à jour l'un de vos fragments (dans notre cas, Fragment0), utilisez ce qui suit:

adapter.NotifyDataChange();

adapter.destroyItem(adapter.getContainer(), 0, adapter.getObject().get(0)); // destroys page 0 in the viewPager.

fragment0 = (Fragment0) getSupportFragmentManager().findFragmentByTag(adapter.getFragmentTag(0)); // Gets fragment instance used on page 0.

fragment0.updateFragment() method which include the updates on this fragment

adapter.instantiateItem(adapter.getContainer(), 0); // re-initialize page 0.

Cette solution était basée sur la technique suggérée par Alvaro Luis Bustamante.


1

1.Tout d'abord, vous devez définir la méthode getItemposition dans votre classe Pageradapter 2.Vous devez lire la position exacte de votre View Pager 3.puis envoyer cette position comme emplacement de données de votre nouveau 4.Write update button onclick listener inside the setonPageChange auditeur

ce code de programme est un peu i modifié pour définir l'élément de position particulier uniquement

public class MyActivity extends Activity {

private ViewPager myViewPager;
private List<String> data;
public int location=0;
public Button updateButton;
@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    data = new ArrayList<String>();
    data.add("A");
    data.add("B");
    data.add("C");
    data.add("D");
    data.add("E");
    data.add("F");

    myViewPager = (ViewPager) findViewById(R.id.pager);
    myViewPager.setAdapter(new MyViewPagerAdapter(this, data));

      updateButton = (Button) findViewById(R.id.update);

    myViewPager.setOnPageChangeListener(new ViewPager.OnPageChangeListener() {
        @Override
        public void onPageScrolled(int i, float v, int i2) {
             //Toast.makeText(MyActivity.this, i+"  Is Selected  "+data.size(), Toast.LENGTH_SHORT).show();
        }

        @Override
        public void onPageSelected( int i) {
          // here you will get the position of selected page
            final int k = i;
             updateViewPager(k);

        }

        @Override
        public void onPageScrollStateChanged(int i) {

        }
    });
}

private void updateViewPager(final int i) {  
    updateButton.setOnClickListener(new OnClickListener() {

        @Override
        public void onClick(View v) {

            Toast.makeText(MyActivity.this, i+"  Is Selected  "+data.size(), Toast.LENGTH_SHORT).show();
            data.set(i, "Replaced "+i);         
            myViewPager.getAdapter().notifyDataSetChanged();
        }
    });

}

private class MyViewPagerAdapter extends PagerAdapter {

    private List<String> data;
    private Context ctx;

    public MyViewPagerAdapter(Context ctx, List<String> data) {
        this.ctx = ctx;
        this.data = data;
    }

    @Override
    public int getCount() {
        return data.size();
    }

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

    @Override
    public Object instantiateItem(View collection, int position) {          

        TextView view = new TextView(ctx);
        view.setText(data.get(position));
        ((ViewPager)collection).addView(view);            
        return view;
    }

    @Override
    public void destroyItem(View collection, int position, Object view) {
         ((ViewPager) collection).removeView((View) view);
    }

    @Override
    public boolean isViewFromObject(View view, Object object) {
        return view == object;
    }

    @Override
    public Parcelable saveState() {
        return null;
    }

    @Override
    public void restoreState(Parcelable arg0, ClassLoader arg1) {
    }

    @Override
    public void startUpdate(View arg0) {
    }

    @Override
    public void finishUpdate(View arg0) {
    }
}
}

1

ce qui a fonctionné pour moi allait viewPager.getAdapter().notifyDataSetChanged();

et dans l'adaptateur mettant votre code pour mettre à jour la vue à l'intérieur getItemPositioncomme si

@Override
public int getItemPosition(Object object) {

    if (object instanceof YourViewInViewPagerClass) { 
        YourViewInViewPagerClass view = (YourViewInViewPagerClass)object;
        view.setData(data);
    }

    return super.getItemPosition(object);
}

ce n'est peut-être pas la façon la plus correcte de procéder, mais cela a fonctionné (l' return POSITION_NONEastuce a provoqué un crash pour moi, ce n'était donc pas une option)


1

Vous pouvez mettre à jour dynamiquement tous les fragments, vous pouvez le voir en trois étapes.

Dans votre adaptateur:

public class MyPagerAdapter extends FragmentPagerAdapter {
private static int NUM_ITEMS = 3;
private Map<Integer, String> mFragmentTags;
private FragmentManager mFragmentManager;

public MyPagerAdapter(FragmentManager fragmentManager) {
    super(fragmentManager);
    mFragmentManager = fragmentManager;
    mFragmentTags = new HashMap<Integer, String>();
}

// Returns total number of pages
@Override
public int getCount() {
    return NUM_ITEMS;
}

// Returns the fragment to display for that page
@Override
public Fragment getItem(int position) {
    switch (position) {
        case 0:
            return FirstFragment.newInstance();
        case 1:
            return SecondFragment.newInstance();
        case 2:
            return ThirdFragment.newInstance();
        default:
            return null;
    }
}

// Returns the page title for the top indicator
@Override
public CharSequence getPageTitle(int position) {
    return "Page " + position;
}

@Override
public Object instantiateItem(ViewGroup container, int position) {
    Object object = super.instantiateItem(container, position);
    if (object instanceof Fragment) {
        Fragment fragment = (Fragment) object;
        String tag = fragment.getTag();
        mFragmentTags.put(position, tag);
    }
    return object;
}

public Fragment getFragment(int position) {
    Fragment fragment = null;
    String tag = mFragmentTags.get(position);
    if (tag != null) {
        fragment = mFragmentManager.findFragmentByTag(tag);
    }
    return fragment;
}}

Maintenant dans votre activité:

public class MainActivity extends AppCompatActivity implements ViewPager.OnPageChangeListener{

MyPagerAdapter mAdapterViewPager;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    ViewPager viewPager = (ViewPager) findViewById(R.id.vpPager);
    mAdapterViewPager = new MyPagerAdapter(getSupportFragmentManager());
    viewPager.setAdapter(mAdapterViewPager);
    viewPager.addOnPageChangeListener(this);
}

@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {

}

@Override
public void onPageSelected(int position) {

    Fragment fragment = mAdapterViewPager.getFragment(position);
    if (fragment != null) {
        fragment.onResume();
    }
}

@Override
public void onPageScrollStateChanged(int state) {

}}

Enfin dans votre fragment, quelque chose comme ça:

public class YourFragment extends Fragment {

// newInstance constructor for creating fragment with arguments
public static YourFragment newInstance() {

    return new YourFragment();
}

// Store instance variables based on arguments passed
@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
}

// Inflate the view for the fragment based on layout XML
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
                         Bundle savedInstanceState) {
    return inflater.inflate(R.layout.fragment, container, false);
}


@Override
public void onResume() {
    super.onResume();

    //to refresh your view
    refresh();

}}

Vous pouvez voir le code complet ici .

Merci Alvaro Luis Bustamante.


1

Toujours retourner POSITION_NONEest simple mais un peu inefficace car cela évoque l'instanciation de toutes les pages qui ont déjà instancié.

J'ai créé une bibliothèque ArrayPagerAdapter pour changer dynamiquement des éléments dans PagerAdapters.

En interne, les adaptateurs de cette bibliothèque reviennent POSITION_NONEsurgetItemPosiition() seulement si nécessaire.

Vous pouvez modifier des éléments dynamiquement comme le suivant en utilisant cette bibliothèque.

@Override
protected void onCreate(Bundle savedInstanceState) {
        /** ... **/
    adapter = new MyStatePagerAdapter(getSupportFragmentManager()
                            , new String[]{"1", "2", "3"});
    ((ViewPager)findViewById(R.id.view_pager)).setAdapter(adapter);
     adapter.add("4");
     adapter.remove(0);
}

class MyPagerAdapter extends ArrayViewPagerAdapter<String> {

    public MyPagerAdapter(String[] data) {
        super(data);
    }

    @Override
    public View getView(LayoutInflater inflater, ViewGroup container, String item, int position) {
        View v = inflater.inflate(R.layout.item_page, container, false);
        ((TextView) v.findViewById(R.id.item_txt)).setText(item);
        return v;
    }
}

La bibliothèque Thils prend également en charge les pages créées par Fragments.


1

C'est pour tous ceux comme moi, qui ont besoin de mettre à jour le Viewpager à partir d'un service (ou d'un autre thread d'arrière-plan) et aucune des propositions n'a fonctionné: Après un peu de vérification du journal, j'ai réalisé que la méthode notifyDataSetChanged () ne revient jamais. getItemPosition (objet Object) est appelé un tout se termine là sans traitement supplémentaire. Ensuite, j'ai trouvé dans les documents de la classe parent PagerAdapter (ne fait pas partie des documents des sous-classes), "Les modifications de l'ensemble de données doivent se produire sur le thread principal et doivent se terminer par un appel à notifyDataSetChanged ()". Ainsi, la solution de travail dans ce cas était (en utilisant FragmentStatePagerAdapter et getItemPosition (Object object) définies pour retourner POSITION_NONE):

puis l'appel à notifyDataSetChanged ():

runOnUiThread(new Runnable() {
         @Override
         public void run() {
             pager.getAdapter().notifyDataSetChanged();
         }
     });

1

Vous pouvez ajouter une transformation de pageur sur Viewpager comme ceci

myPager.setPageTransformer(true, new MapPagerTransform());

Dans le code ci-dessous, j'ai changé la couleur de ma vue lors de l'exécution lors du défilement du pager

public class MapPagerTransform implements ViewPager.PageTransformer {


    public void transformPage(View view, float position) {
     LinearLayout  showSelectionLL=(LinearLayout)view.findViewById(R.id.showSelectionLL);

     if (position < 0) {

                showSelectionLL.setBackgroundColor(Color.WHITE);
     }else if (position >0){

                showSelectionLL.setBackgroundColor(Color.WHITE);
     }else {
                showSelectionLL.setBackgroundColor(Color.RED);
     }
   }
}

1

je sais que je suis dam tard mais ça peut aider quelqu'un. J'étends juste la réponse d'acceptation et j'ai également ajouté le commentaire là-dessus.

bien,

la réponse elle-même dit qu'elle est inefficace

donc afin de le faire se rafraîchir uniquement lorsque cela est nécessaire, vous pouvez le faire

private boolean refresh;

public void refreshAdapter(){
    refresh = true;
    notifyDataSetChanged();
}

@Override
public int getItemPosition(@NonNull Object object) {
    if(refresh){
        refresh = false;
        return POSITION_NONE;
    }else{
        return super.getItemPosition(object);
    }
}

1

ViewPager n'a pas été conçu pour prendre en charge le changement de vue dynamique.

J'en ai eu la confirmation en recherchant un autre bug lié à celui-ci https://issuetracker.google.com/issues/36956111 et en particulier https://issuetracker.google.com/issues/36956111#comment56

Cette question est un peu ancienne, mais Google a récemment résolu ce problème avec ViewPager2 . Il permettra de remplacer les solutions faites à la main (non entretenues et potentiellement buggées) par des solutions standard. Cela empêche également de recréer inutilement des vues comme le font certaines réponses.

Pour des exemples de ViewPager2, vous pouvez vérifier https://github.com/googlesamples/android-viewpager2

Si vous souhaitez utiliser ViewPager2, vous devrez ajouter la dépendance suivante dans votre fichier build.gradle:

  dependencies {
     implementation 'androidx.viewpager2:viewpager2:1.0.0-beta02'
  }

Ensuite, vous pouvez remplacer votre ViewPager dans votre fichier xml par:

    <androidx.viewpager2.widget.ViewPager2
        android:id="@+id/pager"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1" />

Après cela, vous devrez remplacer ViewPager par ViewPager2 dans votre activité

ViewPager2 a besoin d'un RecyclerView.Adapter ou d'un FragmentStateAdapter, dans votre cas, il peut s'agir d'un RecyclerView.Adapter

import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;

import java.util.ArrayList;

public class MyAdapter extends RecyclerView.Adapter<MyAdapter.MyViewHolder> {

    private Context context;
    private ArrayList<String> arrayList = new ArrayList<>();

    public MyAdapter(Context context, ArrayList<String> arrayList) {
        this.context = context;
        this.arrayList = arrayList;
    }

    @NonNull
    @Override
    public MyViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        View view = LayoutInflater.from(context).inflate(R.layout.list_item, parent, false);
        return new MyViewHolder(view);
    }

    @Override
    public void onBindViewHolder(@NonNull MyViewHolder holder, int position) {
        holder.tvName.setText(arrayList.get(position));
    }

    @Override
    public int getItemCount() {
        return arrayList.size();
    }

    public class MyViewHolder extends RecyclerView.ViewHolder {
        TextView tvName;

        public MyViewHolder(@NonNull View itemView) {
            super(itemView);
            tvName = itemView.findViewById(R.id.tvName);
        }
    }
} 

Dans le cas où vous utilisiez un TabLayout, vous pouvez utiliser un TabLayoutMediator:

        TabLayoutMediator tabLayoutMediator = new TabLayoutMediator(tabLayout, viewPager, true, new TabLayoutMediator.OnConfigureTabCallback() {
            @Override
            public void onConfigureTab(@NotNull TabLayout.Tab tab, int position) {
                // configure your tab here
                tab.setText(tabs.get(position).getTitle());
            }
        });

        tabLayoutMediator.attach();

Vous pourrez ensuite actualiser vos vues en modifiant les données de votre adaptateur et en appelant la méthode notifyDataSetChanged



0

Je pense que j'ai fait un moyen simple de notifier les modifications de l'ensemble de données:

Tout d'abord, modifiez un peu le fonctionnement de la fonction instantiateItem:

    @Override
    public Object instantiateItem(final ViewGroup container, final int position) {
        final View rootView = mInflater.inflate(...,container, false);
        rootView.setTag(position);
        updateView(rootView, position);
        container.addView(rootView, LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
        mViewPager.setObjectForPosition(rootView, position);
        return rootView;
    }

pour "updateView", remplissez la vue avec toutes les données que vous souhaitez remplir (setText, setBitmapImage, ...).

vérifiez que destroyView fonctionne comme ceci:

    @Override
    public void destroyItem(final ViewGroup container, final int position, final Object obj) {
        final View viewToRemove = (View) obj;
        mViewPager.removeView(viewToRemove);
    }

Supposons maintenant que vous devez modifier les données, faites-le, puis appelez la fonction suivante sur le PagerAdapter:

    public void notifyDataSetChanged(final ViewPager viewPager, final NotifyLocation fromPos,
            final NotifyLocation toPos) {
        final int offscreenPageLimit = viewPager.getOffscreenPageLimit();
        final int fromPosInt = fromPos == NotifyLocation.CENTER ? mSelectedPhotoIndex
                : fromPos == NotifyLocation.MOST_LEFT ? mSelectedPhotoIndex - offscreenPageLimit
                        : mSelectedPhotoIndex + offscreenPageLimit;
        final int toPosInt = toPos == NotifyLocation.CENTER ? mSelectedPhotoIndex
                : toPos == NotifyLocation.MOST_LEFT ? mSelectedPhotoIndex - offscreenPageLimit
                        : mSelectedPhotoIndex + offscreenPageLimit;
        if (fromPosInt <= toPosInt) {
            notifyDataSetChanged();
            for (int i = fromPosInt; i <= toPosInt; ++i) {
                final View pageView = viewPager.findViewWithTag(i);
                mPagerAdapter.updateView(pageView, i);
            }
        }
    }

public enum NotifyLocation {
    MOST_LEFT, CENTER, MOST_RIGHT
}

Par exemple, si vous souhaitez informer toutes les vues affichées par le viewPager que quelque chose a changé, vous pouvez appeler:

notifyDataSetChanged(mViewPager,NotifyLocation.MOST_LEFT,NotifyLocation.MOST_RIGHT);

C'est ça.


0

Pour ce que ça vaut, sur KitKat + il semble que cela adapter.notifyDataSetChanged()suffit pour faire apparaître les nouvelles vues, à condition que vous ayez setOffscreenPageLimitsuffisamment de hauteur. Je peux obtenir le comportement souhaité en faisant viewPager.setOffscreenPageLimit(2).


0

Je l' utilise en fait notifyDataSetChanged()sur ViewPageret CirclePageIndicatoraprès que je l' appelle destroyDrawingCache()sur ViewPageret il fonctionne .. Aucun des autres solutions a fonctionné pour moi.


La question d'origine remonte à près de 2 ans, je suis sûr qu'il existe de nombreuses différences dans l'API Android maintenant que les réponses ici peuvent ne pas être correctes pour toutes les versions de l'API Android.
C0deAttack
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.