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.
- Android Studio Archivo JAR de Google que genera un error de límite de sobrecarga de GC
- ¿Cómo puede obtener la luz azul halo de una tostada en mi tostada personalizado también?
- detectar oscilaciones a izquierda y derecha en Android (onFling y OnGestureListener)
- No se puede establecer el valor DSCP en la aplicación android
- Android - Constructor de actividad vs onCreate
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();
- Prueba de Realm bajo Android
- Android: ¿Cómo crear un TextView circular?
- Impresión del nombre del enum
- Retrofit 2.0 lanzando "IllegalArgumentException: los parámetros @Field solo se pueden usar con la codificación de formularios". ¿Cómo hacer la consulta correcta de la API y arreglarlo?
- ¿Cómo usar JarJar con Android para cambiar nombres de paquetes de tarros externos?
- Android PayPalActividad no en pantalla completa
- ¿Cómo cambio el valor TextView dentro del código Java?
- Android SQLite - ¿qué hace SQLiteDatabase.replace () en realidad?
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);
- ¿Cómo obtener la hora actual del servidor firebase en milisegundos?
- ActionBarDrawerToggle no se puede aplicar a Android.support.v7.widget.Toolbar