notifydataSetChanged sur Adapter mettra à jour avec les nouveaux éléments, mais ne mettra pas à jour les éléments existants

Je ne pouvais pas trouver quelque chose de spécifique concernant mon problème, veuillez lire la suite pour savoir ce que c’est.

J’ai pris le plus grand soin de veiller à ce que, partout dans mon code, je puisse configurer le droit d’appeler notifyDataSetChanged sur l’adaptateur, d’initialiser l’élément itemList une fois, de le transférer à l’adaptateur et de ne pas le réinitialiser.

Cela fonctionne comme un charme et la vue liste se mettra à jour elle-même, mais uniquement pour les nouveaux éléments.

Pour les éléments existants, ListView ne sera pas mis à jour correctement.

Par exemple, si j’ai une liste qui affiche des éléments personnalisés et que je dois la mettre à jour, je le fais.

public void updateList(List newItems) { if (adapter == null) { itemList.addAll(newItems); adapter = new SomeAdapter(layoutInflator, itemList); listView.setAdapter(adapter); } else { // lets find all the duplicates and do all the updating List nonDuplicateItems = new ArrayList(); for (Item newItem : newItems) { boolean isDuplicate = false; for (Item oldItem : itemList) { // are these the same item? if (newItem.id == oldItem.id) { isDuplicate = true; // update the item olditem.text1 = newItem.text1; oldItem.text2 = newItem.text2; } } if (isDuplicate == false) { // add the new item nonDuplicateItems.add(newItem); } } // I have sortinged just adding these new ones to itemList, // but that doesnt seem to make the listview update the // views for the old ones, so I thought thuis might help // by clearing, merging, and then adding back nonDuplicateItems.addAll(itemList); itemList.clear(); itemList.addAll(nonDuplicateItems); // finally notify the adapter/listview adapter.notifyDataSetChanged(); } } 

maintenant, listview sera toujours mis à jour pour afficher les nouveaux éléments, mais il ne mettra pas à jour les vues sur les éléments existants.

Voici le vrai kicker qui me dit que c’est un problème avec les vues: si j’appelle adapter.getItem(position); sur un élément préexistant mis à jour, l’élément renvoyé affichera les modifications mises à jour (c’est-à-dire que text1 et text2 conserveront leurs nouvelles valeurs) même si cela n’est pas reflété dans la listview!

Si j’appelle listView.invalidateViews(); alors la vue liste affichera les mises à jour, mais cela me pose deux problèmes, parfois des scintillements, et parfois, parfois, si je l’appelle et s’exécute avant que notifyDataSetChanged puisse finir de parvenir à la liste, je reçois une “vue liste”. non notifié du changement de données “error!

Est-ce que quelqu’un sait quelque chose à ce sujet?

 @Override public View getView(int position, View convertView, ViewGroup parent) { ViewHolder viewHolder; if (convertView == null) { convertView = layoutInflator.inflate(R.layout.item_comment, null); // when the holder is created it will find the child views // it will then call refreshHolder() on itself viewHolder = new ViewHolder(convertView, position); convertView.setTag(viewHolder); } else { viewHolder = ((ViewHolder) convertView.getTag()); viewHolder.refreshHolder(position); } return convertView; } public void refreshHolder(int position) { this.position = position; tvText1.setText(getItem(position).text1); tvText2.setText(getItem(position).text2); } 

Je me demande si ce que je devrais faire est de ré-instancier tous mes éléments avant de les append à la liste, en utilisant un constructeur de copie. Peut-être que lors de la notification de l’adaptateur, l’adaptateur supposera qu’il n’y a pas de changement si l’ item est toujours la même référence, et donc ne redessinera pas cette vue? ou peut-être que l’adaptateur dessine uniquement les nouvelles vues pour les nouveaux éléments lorsqu’il en est informé?

Pour append un autre détail, si je fais défiler l’écran en faisant disparaître la vue mise à jour, puis que j’y reviens, les informations correctes s’affichent à mesure que la vue en liste rafraîchit / refait cette vue.

Je suppose que j’ai besoin de la listview pour actualiser toutes ses vues actuelles, invalidateViews(); peut être ce que je dois faire.

Quelqu’un en sait-il plus là-dessus?

EDIT: Comme demandé ici est un adaptateur qui aurait ce problème.

 public class ItemAdapter extends BaseAdapter { private final static int VIEWTYPE_PIC = 1; private final static int VIEWTYPE_NOPIC = 0; public List items; LayoutInflater layoutInflator; ActivityMain activity; public ItemAdapter(List items, LayoutInflater layoutInflator, ActivityMain activity) { super(); this.items = new ArrayList(); updateItemList(items); this.layoutInflator = layoutInflator; this.activity = activity; } public void updateItemList(List updatedItems) { if (updatedItems != null && updatedItems.size() > 0) { // FIND ALL THE DUPLICATES AND UPDATE IF NESSICARY List nonDuplicateItems = new ArrayList(); for (Item newItem : updatedItems) { boolean isDuplicate = false; for (Item oldItem : items) { if (oldItem.getId().equals(newItem.getId())) { // IF IT IS A DUPLICATE, UPDATE THE EXISTING ONE oldItem.update(newItem); isDuplicate = true; break; } } // IF IT IS NOT A DUPLICATE, ADD IT TO THE NON-DUPLICATE LIST if (isDuplicate == false) { nonDuplicateItems.add(newItem); } } // MERGE nonDuplicateItems.addAll(items); // SORT Collections.sort(nonDuplicateItems, new Item.ItemOrderComparator()); // CLEAR this.items.clear(); // ADD BACK IN this.items.addAll(nonDuplicateItems); // REFRESH notifyDataSetChanged(); } } public void removeItem(Item item) { items.remove(item); notifyDataSetChanged(); } @Override public int getCount() { if (items == null) return 0; else return items.size(); } @Override public Item getItem(int position) { if (items == null || position > getCount()) return null; else return items.get(position); } @Override public long getItemId(int position) { return getItem(position).hashCode(); } @Override public int getItemViewType(int position) { Item item = getItem(position); if (item.getPhotoURL() != null && URLUtil.isValidUrl(item.getPhotoURL()) == true) { return VIEWTYPE_PIC; } return VIEWTYPE_NOPIC; } @Override public View getView(int position, View convertView, ViewGroup parent) { ItemHolder itemHolder; if (convertView == null) { if (getItemViewType(position) == VIEWTYPE_PIC) { convertView = layoutInflator.inflate(R.layout.item_pic, null); } else { convertView = layoutInflator.inflate(R.layout.item, null); } // THIS CONSTRUCTOR ALSO CALLS REFRESH ON THE HOLDER FOR US itemHolder = new ItemHolder(convertView, position); convertView.setTag(itemHolder); } else { itemHolder = ((ItemHolder) convertView.getTag()); itemHolder.refreshHolder(position); } return convertView; } @Override public int getViewTypeCount() { return 2; } @Override public boolean hasStableIds() { return false; } @Override public boolean isEmpty() { return (getCount() < 1); } @Override public boolean areAllItemsEnabled() { return true; } @Override public boolean isEnabled(int position) { return true; } } 

Ok j’ai maintenant essayé ça

  @Override public boolean hasStableIds() { return true; } @Override public long getItemId(int position) { return getItem(position).hashCode(); } 

et ça

  @Override public boolean hasStableIds() { return false; } @Override public long getItemId(int position) { return getItem(position).hashCode(); } 

où mon code de hachage est un constructeur de reflection d’Apache utilisé de la sorte (le travail devrait-il provoquer des modifications de hachage en fonction des valeurs)

  @Override public int hashCode() { return HashCodeBuilder.reflectionHashCode(this); } 

et ça n’a pas marché. D’après ce que je peux dire, stableIds ne fait rien.

MODIFIER:

aucune de celles-ci ne fonctionne non plus, quelle que soit la combinaison d’identifiants stables. Encore une fois, et comme toujours, vous devez faire défiler la vue hors écran, puis la rallumer pour qu’elle soit mise à jour.

 listview.refreshDrawableState(); listview.requestLayout(); listview.invalidateViews(); 

Il existe un problème similaire avec une solution qui peut fonctionner:

ListView n’actualise pas les éléments déjà visibles

Avec les “ID instables”, tout devrait bien se notifyDatasetChanged () si vous appelez notifyDatasetChanged () , mais il semble que votre ListView ne sache pas que certains éléments existants doivent être mis à jour.

Peut-être pourriez-vous essayer d’implémenter des identifiants stables et les utiliser de manière à ce que l’identifiant change lors de la mise à jour des éléments.

 @Override public long getItemId(int position) { return getItem(position).getId(); } @Override public boolean hasStableIds() { return true; } 

Une autre approche “brutale” consisterait à créer un nouvel adaptateur et à définir le nouvel adaptateur pour ListView.

J’ai analysé le code et le problème dans lequel vous vous trouvez et je suis arrivé à la conclusion suivante:

La méthode refreshHolder est uniquement appelée lorsque l’object convertView a une valeur dans le convertView .

Cela implique que lorsque convertView n’est affecté à aucune valeur, aucune mise à jour n’est effectuée.

Une solution à cela consiste à déplacer itemHolder.refreshHolder(position) hors du bloc de conditions if-else que vous avez inséré.

adapter.notifyDataSetChanged () doit effectuer le travail. Vous devez être sûr que si ListList que vous manipulez en dehors de l’adaptateur est exactement la même instance que l’adaptateur tient en interne. Si notifyDataSetChanged () ne fonctionne pas pour vous, c’est bien le cas.

Votre adaptateur peut également contenir une “copie” de la liste que vous avez fournie dans le constructeur. Par conséquent, vos modifications dans la liste d’origine ne seront pas répercutées … vous pouvez peut-être introduire une méthode comme suit : adapter.setItems (List éléments) pour vous assurer que les éléments sont vraiment configurés

Vous n’avez pas besoin d’appeler invalidateViews () sur un ListView … tout ce que vous avez à faire est de vous assurer que l’adaptateur dispose de la liste correcte à afficher et à déclencher notifyDataSetChanged () .

au lieu de :

 @Override public int getViewTypeCount() { return 2; } 

Essayez:

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

et au lieu de mettre

 ViewHolder viewHolder; 

dans une méthode getivew (), essayez de le mettre dans un début de classe avant le constructeur de la classe

Au lieu d’utiliser convertView.set/getTag() , pourquoi ne pas mettre à jour directement les vues

 refreshHolder(convertView, position); void refreshHolder(View v, int position) { ((TextView)v.findViewById(R.id.Text1)).setText(getItem(position).text1); ((TextView)v.findViewById(R.id.Text2)).setText(getItem(position).text2); } 

setTag / getTag ne sera pas cohérent sur convertView car vous réutiliserez les vues, et la même vue sera réutilisée lorsqu’une vue défilera hors de la vue et retournera un ViewHolder erroné. Donc, la plupart du temps, la vue n’est pas mise à jour

Après de nombreux essais et erreurs, voici ce qui a fonctionné.

 public class AutoRefreshListView extends ListView { public AutoRefreshListView(Context context) { super(context); } public AutoRefreshListView(Context context, AtsortingbuteSet attrs) { super(context, attrs); } public AutoRefreshListView(Context context, AtsortingbuteSet attrs, int defStyle) { super(context, attrs, defStyle); } private DataSetObserver mDataSetObserver = new AdapterDataSetObserver(); private ListAdapter mAdapter; class AdapterDataSetObserver extends DataSetObserver { @Override public void onChanged() { super.onChanged(); Log.d("AutoRefreshListView", "onChanged"); refreshVisibleViews(); } @Override public void onInvalidated() { super.onInvalidated(); Log.d("AutoRefreshListView", "onInvalidated"); refreshVisibleViews(); } } @Override public void setAdapter(ListAdapter adapter) { super.setAdapter(adapter); if (mAdapter != null) { mAdapter.unregisterDataSetObserver(mDataSetObserver); } mAdapter = adapter; mAdapter.registerDataSetObserver(mDataSetObserver); } public void refreshVisibleViews() { Log.d("AutoRefreshListView", "refresh"); if (mAdapter != null) { for (int i = getFirstVisiblePosition(); i <= getLastVisiblePosition(); i++) { final int dataPosition = i - getHeaderViewsCount(); final int childPosition = i - getFirstVisiblePosition(); if (dataPosition >= 0 && dataPosition < mAdapter.getCount() && getChildAt(childPosition) != null) { Log.d("AutoRefreshListView", "onInvalidated -> Refreshing view (data=" + dataPosition + ",child=" + childPosition + ")"); mAdapter.getView(dataPosition, getChildAt(childPosition), AutoRefreshListView.this); } } } } } 

la solution est d’ici

ListView n’actualise pas les éléments déjà visibles

trouvé par Kupsef

ou vous pouvez le faire de cette manière: listadapter.clear(); listadapter.addAll(yourData); listadapter.clear(); listadapter.addAll(yourData);