J'utilise le ViewPager de la bibliothèque de compatibilité. Je l'ai réussi à afficher plusieurs vues que je peux parcourir.
Cependant, j'ai du mal à comprendre comment mettre à jour ViewPager avec un nouvel ensemble de vues.
J'ai essayé toutes sortes de choses, comme appeler mAdapter.notifyDataSetChanged()
, mViewPager.invalidate()
et même créer un tout nouvel adaptateur chaque fois que je souhaitais 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 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 deuxiè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) {
}
}
}
Il y a plusieurs façons d'y parvenir.
La première option est plus facile, mais un peu plus inefficace.
Remplacez getItemPosition
dans votre PagerAdapter
comme ceci:
public int getItemPosition(Object object) {
return POSITION_NONE;
}
Ainsi, lorsque vous appelez notifyDataSetChanged()
, le pageur de vues 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 (auparavant alvarolb) , consiste à setTag()
dans instantiateItem()
lors de l’instanciation d’une nouvelle vue. Ensuite, au lieu d'utiliser notifyDataSetChanged()
, vous pouvez utiliser findViewWithTag()
pour rechercher la vue que vous souhaitez mettre à jour.
La seconde approche est très flexible et très performante. Félicitations à Alvarolb pour la recherche originale.
Je ne pense pas qu'il y ait un quelconque 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.
Au cours des derniers jours, j'ai travaillé avec PagerAdapter
et ViewPager
et j'ai découvert les éléments suivants:
La méthode notifyDataSetChanged()
sur PagerAdapter
informera uniquement ViewPager
que les pages sous-jacentes ont été modifiées. Par exemple, si vous avez créé/supprimé des pages de manière dynamique (en ajoutant ou en supprimant des éléments de votre liste), ViewPager
devrait s'en charger. Dans ce cas, je pense que ViewPager
dé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 appel notifyDataSetChanged()
, prend ses vues enfant et vérifie leur position avec la fonction getItemPosition()
. Si, pour une vue enfant, cette méthode retourne POSITION_NONE
, ViewPager
comprend que la vue a été supprimée, en appelant la destroyItem()
et en supprimant cette vue.
De cette manière, remplacer getItemPosition()
pour toujours renvoyer POSITION_NONE
est totalement faux si vous souhaitez uniquement mettre à jour le contenu des pages, car les vues créées précédemment seront détruites et de nouvelles vues seront créées chaque fois que vous appelez notifyDatasetChanged()
. Cela peut sembler ne pas être si faux que pour quelques TextView
s, mais lorsque vous avez des vues complexes, telles que ListViews renseignées à partir d'une base de données, cela peut être un réel problème et un gaspillage de ressources.
Il existe donc plusieurs approches pour modifier efficacement le contenu d'une vue sans avoir à la supprimer et à l'instancier à nouveau. Cela dépend du problème que vous souhaitez résoudre. Mon approche consiste à utiliser la méthode setTag()
pour toute vue instanciée dans la méthode instantiateItem()
. Ainsi, lorsque vous souhaitez modifier les données ou invalider la vue dont vous avez besoin, vous pouvez appeler la méthode findViewWithTag()
sur ViewPager
pour récupérer la vue précédemment instanciée et la modifier/utiliser à votre guise sans avoir à supprimer/créer une nouvelle vue à chaque fois. vous voulez mettre à jour une valeur.
Imaginons par exemple que vous avez 100 pages avec 100 TextView
s et que vous souhaitez uniquement mettre à jour une valeur périodiquement. Avec les approches expliquées précédemment, cela signifie que vous supprimez et instanciez 100 TextView
s sur chaque mise à jour. Cela n'a aucun sens...
Remplacez FragmentPagerAdapter
par FragmentStatePagerAdapter
.
Remplacez la méthode getItemPosition()
et renvoyez POSITION_NONE
.
Finalement, il écoutera le paginateur notifyDataSetChanged()
on view.
La réponse donnée par alvarolb est certainement la meilleure façon de le faire. Sur la base de sa réponse, un moyen simple de le mettre en œuvre 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;
}
Ensuite, en redéfinissant la méthode notifyDataSetChanged
, vous pouvez actualiser 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 instantiateItem
et notifyDataSetChanged
pour actualiser votre vue. Dans mon code, j'utilise exactement la même méthode.
Avait 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) {
}
Après des heures de frustration en essayant toutes les solutions ci-dessus pour surmonter ce problème et en essayant de nombreuses solutions sur d'autres questions similaires, telles que ceci , ceci et ceci que tous ont échoué avec moi pour résoudre ce problème. et faire en sorte que ViewPager
détruise l'ancien Fragment
et remplisse pager
par le nouveau Fragment
s. J'ai résolu le problème comme suit:
1) Transforme la classe ViewPager
en extension FragmentPagerAdapter
comme suit:
public class myPagerAdapter extends FragmentPagerAdapter {
2) Créez un élément pour la ViewPager
qui stocke la title
et la fragment
comme suit:
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) Demander au constructeur de ViewPager
de prendre mon instance FragmentManager
pour la stocker dans ma class
comme suit:
private FragmentManager mFragmentManager;
private ArrayList<PagerItem> mPagerItems;
public MyPagerAdapter(FragmentManager fragmentManager, ArrayList<PagerItem> pagerItems) {
super(fragmentManager);
mFragmentManager = fragmentManager;
mPagerItems = pagerItems;
}
4) Créez une méthode pour redéfinir les données adapter
avec les nouvelles données en supprimant toute la précédente fragment
de la fragmentManager
elle-même afin que la adapter
définisse à nouveau la nouvelle fragment
de la nouvelle liste 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) Dans le conteneur Activity
ou Fragment
, ne réinitialisez pas l'adaptateur avec les nouvelles données. Définissez les nouvelles données à l'aide de la méthode setPagerItems
avec 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.
J'ai eu le même problème et ma solution est de remplacer ViewPagerAdapter#getItemId(int position)
:
@Override
public long getItemId(int position) {
return mPages.get(position).getId();
}
Par défaut, cette méthode retourne la position de l'élément. Je suppose que ViewPager
vérifie si itemId
a été modifié et ne recrée la page que si c'était le cas. Mais la version non annulée renvoie la même position que itemId
même si la page est en réalité différente, et ViewPager ne définit pas cette page comme remplacée et devant être recréée.
Pour utiliser ceci, long id
est nécessaire pour chaque page. Normalement, on s'attend à ce qu'il soit unique, mais je suggère, dans ce cas, qu'il soit simplement différent de la valeur précédente pour la même page. Donc, il est possible d'utiliser ici un compteur continu dans un entier adaptateur ou aléatoire (avec une distribution large).
Je pense qu'il est plus cohérent d'utiliser plutôt les balises de vue mentionnées comme solution dans cette rubrique. Mais probablement pas pour tous les cas.
Merci rui.araujo et Alvaro Luis Bustamante. Au début, j'essaie d'utiliser la méthode de rui.araujo, parce que c'est facile. Cela fonctionne mais lorsque les données changent, la page se redessinera évidemment. C'est mauvais alors j'essaie d'utiliser le chemin 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();
}
}
Deux ans et demi après que le PO eut posé sa question, cette question est toujours d'actualité. Il est évident que la priorité de Google dans ce domaine n’est pas particulièrement élevée. Par conséquent, plutôt que de trouver une solution, j’ai trouvé une solution de contournement. La grande avancé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 ne sont pas correctement actualisées, ma solution de contournement était évidente:
Dans mon fragment (les pages):
Dans mon activité, où je fais le chargement des pages:
Après cela, lorsque vous rechargez un deuxième ensemble de pages, le bogue provoquera toujours l'affichage d'anciennes données. Cependant, elles seront maintenant actualisées et vous verrez les nouvelles données. Vos utilisateurs ne sauront pas que la page a toujours été incorrecte, car cette actualisation aura lieu avant même de voir la page.
J'espère que cela aide quelqu'un!
Un moyen beaucoup plus simple: utilisez une FragmentPagerAdapter
et encapsulez vos vues paginées sur des fragments. Ils sont mis à jour
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. Il suffit donc de changer " extend FragmentPagerAdapter " on " extend FragmentStatePagerAdapter "
Juste au cas où quelqu'un utiliserait l'adaptateur FragmentStatePagerAdapter based (qui permettra à ViewPager de créer un minimum de pages pour l'affichage, au plus 2 pour mon cas), la réponse de @ rui.araujo consistant à écraser getItemPosition dans votre adaptateur ne causera pas de gaspillage important , mais cela peut encore ê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;
}
Toutes ces solutions ne m'ont pas aidé. J'ai donc trouvé une solution qui fonctionne: Vous pouvez setAdapter
à chaque fois, mais cela ne suffit pas . vous devriez 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);
J'ai eu un problème similaire dans lequel j'avais quatre pages et une des pages a mis à jour des vues sur les trois autres. J'ai pu mettre à jour les widgets (SeekBars, TextViews, etc.) de la page adjacente à la page en cours. Les deux dernières pages auraient des widgets non initialisés lors de l'appel de mTabsAdapter.getItem(position)
.
Pour résoudre mon problème, j'ai utilisé setSelectedPage(index)
avant d'appeler getItem(position)
. Cela instancierait la page, ce qui me permettrait de modifier les valeurs et les widgets de chaque page.
Après toute la mise à jour, j'utiliserais setSelectedPage(position)
suivi de notifyDataSetChanged()
.
Vous pouvez voir un léger scintillement dans ListView sur la page de mise à jour principale, mais rien ne se remarque. Je ne l'ai pas testé complètement, mais cela résout mon problème immédiat.
Après de nombreuses recherches sur ce problème, j’ai trouvé une très bonne solution qui, à mon avis, est la bonne façon de procéder. Pour l'essentiel, instantiateItem n'est appelé que lorsque la vue est instanciée et plus jamais si la vue n'est pas détruite (c'est ce qui se produit lorsque vous substituez la fonction getItemPosition pour renvoyer POSITION_NONE). Au lieu de cela, ce que vous voulez faire est enregistrer les vues créées et les mettre à jour dans l'adaptateur, générer une fonction get afin que quelqu'un d'autre puisse le mettre à jour, ou une fonction set mettant à 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;
}
alors, 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!
Je ne fais que poster cette réponse au cas où quelqu'un le trouverait utile. Pour faire exactement la même chose, j'ai simplement pris le code source de ViewPager et de PagerAdapter de la bibliothèque de compatibilité et l'ai compilé dans mon code (vous devez trier toutes les erreurs et les importer vous-même, mais vous pouvez le faire sans aucun doute).
Ensuite, dans CustomViewPager, créez une méthode appelée updateViewAt (int position). La vue elle-même peut être obtenue à partir des mItems ArrayList définis dans la classe ViewPager (vous devez définir un ID pour les vues à instancier et comparer cet identifiant à la position dans la méthode updateViewAt ()). Ensuite, vous pouvez mettre à jour la vue si nécessaire.
Vous pouvez mettre à jour dynamiquement tous les fragments, vous pouvez 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.
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 d'affichage au moment de l'exécution lorsque le défilement par 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);
}
}
}
Toujours retourner POSITION_NONE
est simple mais un peu inefficace car cela évoque l’instanciation de toutes les pages déjà instanciées.
J'ai créé un library ArrayPagerAdapter pour modifier les éléments de PagerAdapters de manière dynamique.
En interne, les adaptateurs de cette bibliothèque renvoient POSITION_NONE
sur getItemPosiition()
uniquement lorsque cela est nécessaire.
Vous pouvez modifier les éléments de manière dynamique, comme suit, à l'aide de 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.
Ceci concerne tous ceux qui, comme moi, doivent mettre à jour le Viewpager à partir d'un service (ou d'un autre fil d'arrière-plan) et aucune des propositions n'a fonctionné: Après un peu de vérification, je me suis rendu compte que la méthode notifyDataSetChanged () ne renvoie jamais. getItemPosition (Object Object) est appelé un tout se termine sans traitement supplémentaire. Ensuite, j’ai trouvé dans la documentation de la classe parent PagerAdapter (n’est pas dans la documentation des sous-classes), "Les modifications du fichier doivent se produire sur le fil principal et doivent se terminer par un appel à notifyDataSetChanged ()" . La solution de travail dans ce cas était (en utilisant FragmentStatePagerAdapter et getItemPosition (Object object) défini pour renvoyer POSITION_NONE):
puis l'appel à notifyDataSetChanged ():
runOnUiThread(new Runnable() {
@Override
public void run() {
pager.getAdapter().notifyDataSetChanged();
}
});
Je suppose que j'ai la logique de ViewPager.
Si j'ai besoin d'actualiser un ensemble de pages et de les afficher en fonction d'un nouvel ensemble de données, j'appelle notifyDataSetChanged () . Ensuite, ViewPager effectue un nombre d'appels vers getItemPosition () , en y passant le fragment sous forme d'objet. Ce fragment peut provenir soit d'un ancien jeu de données (que je veux supprimer), soit d'un nouveau (que je veux afficher). Donc, je substitue getItemPosition () et je dois déterminer si mon fragment provient de l’ancien jeu de données ou du nouveau.
Dans mon cas, j'ai une disposition en deux volets avec une liste des principaux éléments dans le volet de gauche et une vue par balayage (ViewPager) à droite. Ainsi, je stocke un lien vers mon élément principal actuel dans mon PagerAdapter ainsi que dans chaque fragment de page instanciée . Lorsque l'élément sélectionné dans la liste change, je stocke le nouvel élément principal dans PagerAdapter et appelle notifyDataSetChanged ( ) . Et dans le fichier getItemPosition () substitué, je compare le premier élément de mon adaptateur au premier élément de mon fragment. Et seulement s'ils ne sont pas égaux, je retourne POSITION_NONE. Ensuite, PagerAdapter ré-instancie tous les fragments qui ont renvoyé POSITION_NONE.
REMARQUE. Stocker l'id d'élément supérieur au lieu d'une référence pourrait être une meilleure idée.
L'extrait de code ci-dessous est un peu schématique, mais je l'ai adapté à partir 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 pour tous les chercheurs précédents!
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, dans 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 GLSurfaceView qui affiche un objet 3D basé sur un chemin d'accès à un fichier .ply. Ainsi, dans ma méthode updateFragment, je change le chemin d'accès à ce fichier ply.
puis créez une instance 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, dans 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 les éléments suivants:
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.
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 de getItemPosition
comme si
@Override
public int getItemPosition(Object object) {
if (object instanceof YourViewInViewPagerClass) {
YourViewInViewPagerClass view = (YourViewInViewPagerClass)object;
view.setData(data);
}
return super.getItemPosition(object);
}
ce n’était peut-être pas la façon la plus correcte d’agir, mais cela fonctionnait (l’astuce de return POSITION_NONE
provoquait un crash pour moi, donc ce n’était pas une option)
ViewPager n'a pas été conçu pour prendre en charge le changement de vue dynamique.
J'en ai eu la confirmation en cherchant un autre bogue 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 . Cela permettra de remplacer les solutions faites à la main (non gérées et potentiellement boguées) par des solutions standard. Cela évite également de recréer inutilement des vues comme certaines réponses.
Pour les exemples 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 nécessite soit un RecyclerView.Adapter, soit un FragmentStateAdapter, dans votre cas cela peut être 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 TabLayout, vous pouvez utiliser 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.
Dans mon cas, chaque fois que Fragment est entré dans @override onCreateView(...)
i, je redémarrais des valeurs, donc gérez les valeurs nulles ou vides comme
if(var == null){
var = new Something();
}
if(list.isEmpty()){
list = new ArrayList<MyItem>();
}
if(myAdapter == null){
myAdapter = new CustomAdapter();
recyclerView.setAdapter(myAdapter);
}
// etc...
La classe Fragment peut entrer dans onCreateView chaque fois que nous changeons et revenons à la vue depuis l'interface utilisateur au moment de l'exécution.
1.Vous devez d’abord définir la méthode getItemposition dans votre classe Pageradapter 2.Vous devez lire la position exacte de votre View Pager .Write update button onclick listener dans l’écouteur setonPageChange
ce code de programme est un petit 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) {
}
}
}
J'utilise réellement notifyDataSetChanged()
sur ViewPager
et CirclePageIndicator
et ensuite j'appelle destroyDrawingCache()
sur ViewPager
et cela fonctionne .. Aucune des autres solutions n'a fonctionné pour moi.
J'utilise Tablayout avec ViewPagerAdapter. Pour transmettre des données entre fragments ou pour communiquer entre fragments, utilisez le code ci-dessous qui fonctionne parfaitement et actualise le fragment chaque fois qu'il apparaît. A l'intérieur, le clic du bouton du deuxième fragment écrit en dessous du code.
b.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
String text=e1.getText().toString(); // get the text from EditText
// move from one fragment to another fragment on button click
TabLayout tablayout = (TabLayout) getActivity().findViewById(R.id.tab_layout); // here tab_layout is the id of TabLayout which is there in parent Activity/Fragment
if (tablayout.getTabAt(1).isSelected()) { // here 1 is the index number of second fragment i-e current Fragment
LocalBroadcastManager lbm = LocalBroadcastManager.getInstance(getContext());
Intent i = new Intent("EDIT_TAG_REFRESH");
i.putExtra("MyTextValue",text);
lbm.sendBroadcast(i);
}
tablayout.getTabAt(0).select(); // here 0 is the index number of first fragment i-e to which fragment it has to moeve
}
});
ci-dessous est le code qui doit être écrit dans le premier fragment (dans mon cas) i-e dans Fragment récepteur.
MyReceiver r;
Context context;
String newValue;
public void refresh() {
//your code in refresh.
Log.i("Refresh", "YES");
}
public void onPause() {
super.onPause();
LocalBroadcastManager.getInstance(context).unregisterReceiver(r);
}
public void onResume() {
super.onResume();
r = new MyReceiver();
LocalBroadcastManager.getInstance(getActivity()).registerReceiver(r,
new IntentFilter("EDIT_TAG_REFRESH"));
} // this code has to be written before onCreateview()
// below code can be written any where in the fragment
private class MyReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
PostRequestFragment.this.refresh();
String action = intent.getAction();
newValue=intent.getStringExtra("MyTextValue");
t1.setText(newValue); // upon Referesh set the text
}
}
Au lieu de renvoyer POSITION_NONE
et de créer à nouveau tous les fragments, vous pouvez procéder comme je le suggère ici: Mettre à jour ViewPager de manière dynamique?
Quelque chose qui a fonctionné pour moi a été de remplacer la méthode finishUpdate
, qui est appelée à la fin d’une transition et d’effectuer la notifyDataSetChanged()
à cet endroit:
public class MyPagerAdapter extends FragmentPagerAdapter {
...
@Override
public void finishUpdate(ViewGroup container) {
super.finishUpdate(container);
this.notifyDataSetChanged();
}
...
}
La bonne façon de remplacer getItemPosition ():
@Override
public int getItemPosition(@NonNull Object object) {
if (!(object instanceof MyPageView)) {
// Should never happen
return super.getItemPosition(object);
}
MyDataObject dataObject = ((MyPageView) object).getData();
if (!(dataObject instanceof MyDataObject)) {
// Should never happen
return super.getItemPosition(object);
}
if (lstItems.contains(dataObject)) {
return POSITION_UNCHANGED;
}
return POSITION_NONE;
}
En bref, vous devez attacher/enregistrer les données que vous utilisez pour peindre votre vue, à votre vue. À partir de là, lorsque getItemPosition () est appelé, le ViewPager peut déterminer si la vue actuellement affichée est toujours présente sur l'adaptateur et agir en conséquence.
Vous avez juste besoin de ce code simple:
Code:
private void updateAdapter(){
if(adapterViewPager != null){
int from = vpMyViewPager.getCurrentItem() - vpMyViewPager.getOffscreenPageLimit();
int to = vpMyViewPager.getCurrentItem() + vpMyViewPager.getOffscreenPageLimit();
vpMyViewPager.removeAllViews();
for(int i = from; i <= to; i++){
if(i < 0){
continue;
}
adapterViewPager.instantiateItem(vpMyViewPager, i);
}
}
}
Explication:
Si vous n'avez pas changé offscreenPageLimit
de ViewPager
, il y a toujours 3 à 4 enfants selon la direction dans laquelle vous vous dirigez. Et pour afficher le contenu correct à l'intérieur de ses enfants, il utilise l'adaptateur pour obtenir le contenu correct . Maintenant, lorsque vous appelez removeAllViews()
sur votre ViewPager
, seules 3 à 4 View
s sont réellement supprimées de la hiérarchie de Window
et En appelant instantiateItem(ViewGroup viewPager, int index)
, vous ne recréez que 3 à 4 View
s. Ensuite, tout est revenu à la normale, vous glissez et faites défiler, la ViewPager
affiche le contenu à l'aide de son adaptateur. Le nombre de ses enfants n’est pas le même sur tous les appareils dans toutes les situations. Par exemple, si vous définissez offscreenPageLimit
à 5, il aura probablement entre 11 et 12 enfants, mais c’est tout, c’est peu. c'est rapide.
Je pense avoir créé un moyen simple de notifier les modifications apportées aux ensembles 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 deviez modifier les données, le faire, puis appeler 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 tout.
je sais que je suis en retard, mais ça peut aider quelqu'un. Je ne fais que prolonger la réponse et j'ai également ajouté le commentaire à ce sujet.
bien,
la réponse elle-même dit qu'il est inefficace
donc afin de le rafraîchir seulement si 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);
}
}
La meilleure solution de mon expérience: https://stackoverflow.com/a/44177688/3118950 , qui remplace la fonction long getItemId()
et renvoie un ID unique au lieu de la position par défaut. De plus, cette réponse est importée pour signaler que l'ancien fragment sera conservé dans le gestionnaire de fragments au cas où le montant total serait inférieur à la limite de pages et que onDetach()
/onDestory()
ne serait pas appelé lors du remplacement du fragment.
Pour ce que cela vaut, sur KitKat +, il semble que adapter.notifyDataSetChanged()
est suffisant pour faire apparaître les nouvelles vues, à condition que vous ayez setOffscreenPageLimit
suffisamment élevé. Je peux obtenir le comportement souhaité en faisant viewPager.setOffscreenPageLimit(2)
.
C'est un problème horrible et je suis heureux de présenter une excellente solution. simple, efficace et efficace!
Voir ci-dessous, le code montre l'utilisation d'un drapeau pour indiquer quand pour renvoyer POSITION_NONE
public class ViewPagerAdapter extends PagerAdapter
{
// Members
private boolean mForceReinstantiateItem = false;
// This is used to overcome terrible bug that Google isn't fixing
// We know that getItemPosition() is called right after notifyDataSetChanged()
// Therefore, the fix is to return POSITION_NONE right after the notifyDataSetChanged() was called - but only once
@Override
public int getItemPosition(Object object)
{
if (mForceReinstantiateItem)
{
mForceReinstantiateItem = false;
return POSITION_NONE;
}
else
{
return super.getItemPosition(object);
}
}
public void setData(ArrayList<DisplayContent> newContent)
{
mDisplayContent = newContent;
mForceReinstantiateItem = true;
notifyDataSetChanged();
}
}
Je laisse ici ma propre solution, qui est une solution de contournement, car il semble que le problème est que FragmentPagerAdapter
ne nettoie pas la précédente fragments
, vous pouvez être ajouté à ViewPager
dans le gestionnaire de fragments. Donc, je crée une méthode à exécuter avant d'ajouter le FragmentPagerAdapter
:
(Dans mon cas, je n’ajoute jamais plus de 3 fragments, mais vous pouvez utiliser par exemple getFragmentManager().getBackStackEntryCount()
et vérifier tous les fragments
.
/**
* this method is solving a bug in FragmentPagerAdapter which don't delete in the fragment manager any previous fragments in a ViewPager.
*
* @param containerId
*/
public void cleanBackStack(long containerId) {
FragmentTransaction transaction = getFragmentManager().beginTransaction();
for (int i = 0; i < 3; ++i) {
String tag = "Android:switcher:" + containerId + ":" + i;
Fragment f = getFragmentManager().findFragmentByTag(tag);
if (f != null) {
transaction.remove(f);
}
}
transaction.commit();
}
Je sais que c'est une solution de contournement, car il cessera de fonctionner si la structure crée les balises.
(actuellement "Android:switcher:" + containerId + ":" + i
)
Ensuite, la façon de l'utiliser est après avoir récupéré le conteneur:
ViewPager viewPager = (ViewPager) view.findViewById(R.id.view_pager);
cleanBackStack(viewPager.getId());