NotifydataSetChanged en Adapter se actualizará con nuevos elementos, pero no actualizará los elementos existentes

No pude encontrar algo específicamente relacionado con mi problema exacto, por favor, lea para averiguar qué es eso.

He tenido mucho cuidado para asegurarse de que en todas partes en mi código, estoy configurado derecho a llamar a notifyDataSetChanged en el adaptador, inicializar el itemList una vez, y pasar que al adaptador, y no volver a inicializar nunca.

Funciona como un encanto, y la vista de lista se actualizará, pero sólo para los nuevos elementos.

Para los elementos existentes, ListView no se actualizará correctamente.

Por ejemplo, si tengo una lista que muestra algunos elementos personalizados, y necesito actualizarlo, hago esto

public void updateList(List<item> 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<item> nonDuplicateItems = new ArrayList<item>(); 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 tried 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(); } } 

Ahora el listview se actualizará siempre para mostrar nuevos elementos, pero no actualizará las vistas de los elementos existentes.

Aquí está el verdadero pateador que me dice que es un problema con las vistas: si llamo a adapter.getItem(position); En un elemento preexistente actualizado, el elemento devuelto mostrará los cambios actualizados, (lo que significa que text1 y text2 mantendrán sus nuevos valores) aunque no se refleje en el listview.

Si llamo a listView.invalidateViews(); A continuación, la vista de lista mostrará las actualizaciones, pero tengo dos problemas con eso, a veces parpadea, ya veces, sólo a veces si lo llamo y se ejecuta antes de que el notifyDataSetChanged puede terminar de llegar a la listview, me sale una "vista de lista No notificado de cambio de datos "!

¿Alguien sabe algo de esto?

 @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); } 

Me pregunto si lo que debo hacer es volver a instanciar todos mis elementos antes de agregar el a la lista, utilizando un constructor de copia. Tal vez al notificar al adaptador, el adaptador asumirá que no hay cambios si el item sigue siendo la misma referencia, y por lo tanto no volver a dibujar esa vista? O tal vez el adaptador sólo dibuja nuevas vistas de nuevos elementos cuando se notifica?

Para añadir otro detalle, si me desplazo hacia abajo haciendo que la vista actualizada salga de la pantalla, y luego volver a ella, muestra la información correcta cuando el listview actualiza / remakes esa vista.

Supongo que necesito el listview para actualizar todas sus vistas actuales así, invalidateViews(); Puede ser lo que tengo que hacer.

¿Alguien sabe más sobre esto?

EDIT: Como se solicita aquí es un adaptador que tendría este problema.

 public class ItemAdapter extends BaseAdapter { private final static int VIEWTYPE_PIC = 1; private final static int VIEWTYPE_NOPIC = 0; public List<Item> items; LayoutInflater layoutInflator; ActivityMain activity; public ItemAdapter(List<Item> items, LayoutInflater layoutInflator, ActivityMain activity) { super(); this.items = new ArrayList<Item>(); updateItemList(items); this.layoutInflator = layoutInflator; this.activity = activity; } public void updateItemList(List<Item> updatedItems) { if (updatedItems != null && updatedItems.size() > 0) { // FIND ALL THE DUPLICATES AND UPDATE IF NESSICARY List<Item> nonDuplicateItems = new ArrayList<Item>(); 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, ahora he probado esto

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

y esto

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

Donde mi hashcode es un constructor de reflexión de apache utilizado como tal (Debería trabajar causar los cambios de hash basados ​​en valores)

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

Y no funcionó. De lo que puedo decir stableIds no está haciendo nada.

EDITAR:

Ninguno de estos trabajos tampoco, en cualquier combinación de Id estable. Una vez más, y lo mismo que siempre, tienes que desplazar la vista fuera de la pantalla y luego volver a encender para que se actualice.

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

Hay un problema similar aquí con una solución que puede funcionar:

ListView no actualiza elementos ya visibles

Con "IDs inestables" todo debería estar bien si llama a notifyDatasetChanged () , pero parece que su ListView no sabe que algunos elementos existentes tienen que ser actualizados.

Tal vez usted puede tratar de implementar ids estables y mal uso de una manera, que el ID cambia en las actualizaciones de elementos.

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

Otro enfoque de "fuerza bruta" sería construir un nuevo adaptador y establecer el nuevo adaptador para el ListView.

Analicé el código y el problema en el que se encuentra y llegué a la siguiente conclusión:

El método refreshHolder sólo se invoca cuando el objeto convertView tiene un valor en el montón.

Esto implica que cuando convertView no está asignado a ningún valor, no se producirá ninguna actualización.

Una solución para esto es mover itemHolder.refreshHolder(position) fuera del bloque de condiciones if-else que ha insertado.

El adaptador.notifyDataSetChanged () debe hacer el trabajo, lo que necesita para asegurarse de que si la lista de elementosLista que está manipulando fuera del adaptador es la misma instancia exacta que el adaptador se mantiene internamente. Si notifyDataSetChanged () no funciona para usted, ese es definitivamente el caso.

Su adaptador también podría tener una "copia" de la lista que proporcionó en el constructor, por lo que sus cambios en la lista original no se reflejan … tal vez puede introducir un método para el adaptador como: adapter.setItems (List Artículos) para asegurar que los artículos están realmente fijados

No es necesario llamar a invalidateViews () en un ListView … todo lo que necesita hacer es asegurarse de que el adaptador tiene la lista correcta para mostrar y activar notifyDataSetChanged () .

En lugar de usar convertView.set/getTag() , ¿por qué no actualizar directamente las vistas

 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 no será coherente en convertView ya que está reutilizando las vistas y la misma vista se reutilizará cuando una vista se despliegue fuera de la vista y devolverá ViewHolder incorrecto. Así que la mayoría de las veces la vista no se actualiza

en lugar de :

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

Tratar :

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

Y en lugar de poner

 ViewHolder viewHolder; 

En un método getivew (), trate de ponerlo en un arranque de clase antes de constructor de clase

Después de mucho ensayo y error, esto es lo que funcionó.

 public class AutoRefreshListView extends ListView { public AutoRefreshListView(Context context) { super(context); } public AutoRefreshListView(Context context, AttributeSet attrs) { super(context, attrs); } public AutoRefreshListView(Context context, AttributeSet 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 solución es de aquí

ListView no actualiza elementos ya visibles

Encontrado por Kupsef

O puede hacerlo de esta manera: listadapter.clear(); listadapter.addAll(yourData); listadapter.clear(); listadapter.addAll(yourData);

  • Android Studio: Código de error 1: Gradle: Error de ejecución para la tarea ': app: processDebugResources'
  • Error de clase duplicado de Android al incluir POI de Apache
  • Android Editar canales de mapa de bits
  • Cómo configurar ProGuard para omitir métodos con SupressWarning ("unused")
  • Void ... params significado en java function declaration
  • ¿Cómo obtener las claves de ContentValues?
  • Ocultar infowindow android map api v2
  • Cómo obtener el tamaño de la tarjeta SD de Android?
  • Cómo publicar a la pared facebook android-sdk: 4.0.0
  • Captura de EditText Foco perdido
  • Cómo obtener la dirección IP y los nombres de todos los dispositivos en la red local en Android
  • FlipAndroid es un fan de Google para Android, Todo sobre Android Phones, Android Wear, Android Dev y Aplicaciones para Android Aplicaciones.