¿Cómo puedo actualizar una sola fila en un ListView?

Tengo un ListView que muestra noticias. Contienen una imagen, un título y algo de texto. La imagen se carga en una notifyDataSetChanged() separada (con una cola y todo) y cuando la imagen se descarga, ahora llamo notifyDataSetChanged() en el adaptador de lista para actualizar la imagen. Esto funciona, pero se getView() con demasiada frecuencia, ya que notifyDataSetChanged() llama a getView() para todos los elementos visibles. Quiero actualizar solo el único elemento en la lista. ¿Cómo haría esto?

Los problemas que tengo con mi enfoque actual son:

  1. Desplazarse es lento
  2. Tengo una animación de fundido en la imagen que ocurre cada vez que se carga una nueva imagen en la lista.

Encontré la respuesta, gracias a tu información, Michelle. De hecho, puede obtener la vista correcta con View#getChildAt(int index) . El truco es que comienza a contar desde el primer elemento visible. De hecho, solo puedes obtener los elementos visibles. Usted resuelve esto con ListView#getFirstVisiblePosition() .

Ejemplo:

 private void updateView(int index){ View v = yourListView.getChildAt(index - yourListView.getFirstVisiblePosition()); if(v == null) return; TextView someText = (TextView) v.findViewById(R.id.sometextview); someText.setText("Hi! I updated you manually!"); } 

Esta pregunta se realizó en Google I / O 2010, puede verla aquí:

El mundo de ListView, hora 52:30

Básicamente lo que Romain Guy explica es llamar a getChildAt(int) en el ListView para obtener la vista y (creo) llamar a getFirstVisiblePosition() para averiguar la correlación entre posición e índice.

Romain también señala el proyecto llamado Shelves como ejemplo, creo que podría querer decir el método ShelvesActivity.updateBookCovers() , pero no puedo encontrar la llamada de getFirstVisiblePosition() .

ACTUALIZACIONES INCREÍBLES VENIDAS:

El RecyclerView solucionará esto en el futuro cercano. Como se señaló en http://www.grokkingandroid.com/first-glance-androids-recyclerview/ , podrá llamar a los métodos para especificar exactamente el cambio, como por ejemplo:

 void notifyItemInserted(int position) void notifyItemRemoved(int position) void notifyItemChanged(int position) 

Además, todos querrán usar las nuevas vistas basadas en RecyclerView porque serán recompensadas con animaciones de aspecto agradable. ¡El futuro se ve increíble! 🙂

Así es como lo hice:

Sus artículos (filas) deben tener identificadores únicos para que pueda actualizarlos más tarde. Establezca la etiqueta de cada vista cuando la lista obtenga la vista desde el adaptador. (También puede usar la etiqueta clave si la etiqueta predeterminada se usa en otro lugar)

 @Override public View getView(int position, View convertView, ViewGroup parent) { View view = super.getView(position, convertView, parent); view.setTag(getItemId(position)); return view; } 

Para la actualización, verifique cada elemento de la lista, si una vista con una identificación dada está visible, entonces realizaremos la actualización.

 private void update(long id) { int c = list.getChildCount(); for (int i = 0; i < c; i++) { View view = list.getChildAt(i); if ((Long)view.getTag() == id) { // update view } } } 

¡En realidad es más fácil que otros métodos y mejor cuando se trata de identificadores y no de posiciones! También debe llamar a la actualización para los artículos que se vuelven visibles.

Las respuestas son claras y correctas. Agregaré una idea para el caso de CursorAdapter aquí.

Si está subclasificando CursorAdapter (o ResourceCursorAdapter , o SimpleCursorAdapter ), puede implementar ViewBinder o anular los bindView() y newView() , estos no reciben el índice del elemento de la lista actual en los argumentos. Por lo tanto, cuando llegan algunos datos y desea actualizar los elementos relevantes de la lista visible, ¿cómo conoce sus índices?

Mi solución fue:

  • mantenga una lista de todas las vistas de elementos de lista creadas, agregue elementos a esta lista desde newView()
  • cuando lleguen los datos, repítelos y vea cuál necesita actualización; mejor que notifyDatasetChanged() y actualizarlos a todos.

Debido a la vista de reciclaje, el número de referencias de vista que necesitaré almacenar e iterar será aproximadamente igual al número de elementos de la lista visibles en la pantalla.

 int wantedPosition = 25; // Whatever position you're looking for int firstPosition = linearLayoutManager.findFirstVisibleItemPosition(); // This is the same as child #0 int wantedChild = wantedPosition - firstPosition; if (wantedChild < 0 || wantedChild >= linearLayoutManager.getChildCount()) { Log.w(TAG, "Unable to get view for desired position, because it's not being displayed on screen."); return; } View wantedView = linearLayoutManager.getChildAt(wantedChild); mlayoutOver =(LinearLayout)wantedView.findViewById(R.id.layout_over); mlayoutPopup = (LinearLayout)wantedView.findViewById(R.id.layout_popup); mlayoutOver.setVisibility(View.INVISIBLE); mlayoutPopup.setVisibility(View.VISIBLE); 

Para RecycleView usa este código

obtener la clase de modelo primero como global, como este objeto de clase modelo

 SampleModel golbalmodel=new SchedulerModel(); 

e inicializarlo en global

obtener la fila actual de la vista por el modelo inicializándola en el modelo global

 SampleModel data = (SchedulerModel) sampleList.get(position); golbalmodel=data; 

establecer el valor cambiado para el método de modelo de objeto global que se establecerá y agregar el notifyDataSetChanged su funciona para mí

 golbalmodel.setStartandenddate(changedate); notifyDataSetChanged(); 

Aquí hay una pregunta relacionada con buenas respuestas.

Utilicé el código que proporcionaba Erik, funciona muy bien, pero tengo un adaptador personalizado complejo para mi lista de lista y tuve que enfrentar dos veces la implementación del código que actualiza la interfaz de usuario. Intenté obtener la nueva vista de mis adaptadores Método getView (la matriz de matrices que contiene los datos de la lista de lista ya se ha actualizado / cambiado):

 View cell = lvOptim.getChildAt(index - lvOptim.getFirstVisiblePosition()); if(cell!=null){ cell = adapter.getView(index, cell, lvOptim); //public View getView(final int position, View convertView, ViewGroup parent) cell.startAnimation(animationLeftIn()); } 

Está funcionando bien, pero no sé si esta es una buena práctica. Así que no necesito implementar el código que actualiza el elemento de la lista dos veces.

Mi solución: si es correcta *, actualice los datos y los elementos visibles sin volver a dibujar toda la lista. Else notifyDataSetChanged.

Correcto – oldData size == nuevo tamaño de datos e ID de datos antiguos y su orden == nuevos ID de datos y orden

Cómo:

 /** * A View can only be used (visible) once. This class creates a map from int (position) to view, where the mapping * is one-to-one and on. * */ private static class UniqueValueSparseArray extends SparseArray { private final HashMap m_valueToKey = new HashMap(); @Override public void put(int key, View value) { final Integer previousKey = m_valueToKey.put(value,key); if(null != previousKey) { remove(previousKey);//re-mapping } super.put(key, value); } } @Override public void setData(final List data) { // TODO Implement 'smarter' logic, for replacing just part of the data? if (data == m_data) return; List oldData = m_data; m_data = null == data ? Collections.EMPTY_LIST : data; if (!updateExistingViews(oldData, data)) notifyDataSetChanged(); else if (DEBUG) Log.d(TAG, "Updated without notifyDataSetChanged"); } /** * See if we can update the data within existing layout, without re-drawing the list. * @param oldData * @param newData * @return */ private boolean updateExistingViews(List oldData, List newData) { /** * Iterate over new data, compare to old. If IDs out of sync, stop and return false. Else - update visible * items. */ final int oldDataSize = oldData.size(); if (oldDataSize != newData.size()) return false; DBObject newObj; int nVisibleViews = m_visibleViews.size(); if(nVisibleViews == 0) return false; for (int position = 0; nVisibleViews > 0 && position < oldDataSize; position++) { newObj = newData.get(position); if (oldData.get(position).getId() != newObj.getId()) return false; // iterate over visible objects and see if this ID is there. final View view = m_visibleViews.get(position); if (null != view) { // this position has a visible view, let's update it! bindView(position, view, false); nVisibleViews--; } } return true; } 

y por supuesto:

 @Override public View getView(final int position, final View convertView, final ViewGroup parent) { final View result = createViewFromResource(position, convertView, parent); m_visibleViews.put(position, result); return result; } 

Ignore el último parámetro de bindView (lo uso para determinar si necesito o no reciclar mapas de bits para ImageDrawable).

Como se mencionó anteriormente, el número total de vistas "visibles" es aproximadamente la cantidad que cabe en la pantalla (ignorando los cambios de orientación, etc.), por lo que no tiene nada que ver con la memoria.

exactamente utilicé esto

 private void updateSetTopState(int index) { View v = listview.getChildAt(index - listview.getFirstVisiblePosition()+listview.getHeaderViewsCount()); if(v == null) return; TextView aa = (TextView) v.findViewById(R.id.aa); aa.setVisibility(View.VISIBLE); } 

Inventé otra solución, como el método RecyclyerView void notifyItemChanged(int position) , creo una clase CustomBaseAdapter como esta:

 public abstract class CustomBaseAdapter implements ListAdapter, SpinnerAdapter { private final CustomDataSetObservable mDataSetObservable = new CustomDataSetObservable(); public boolean hasStableIds() { return false; } public void registerDataSetObserver(DataSetObserver observer) { mDataSetObservable.registerObserver(observer); } public void unregisterDataSetObserver(DataSetObserver observer) { mDataSetObservable.unregisterObserver(observer); } public void notifyDataSetChanged() { mDataSetObservable.notifyChanged(); } public void notifyItemChanged(int position) { mDataSetObservable.notifyItemChanged(position); } public void notifyDataSetInvalidated() { mDataSetObservable.notifyInvalidated(); } public boolean areAllItemsEnabled() { return true; } public boolean isEnabled(int position) { return true; } public View getDropDownView(int position, View convertView, ViewGroup parent) { return getView(position, convertView, parent); } public int getItemViewType(int position) { return 0; } public int getViewTypeCount() { return 1; } public boolean isEmpty() { return getCount() == 0; } { } } 

No se olvide de crear una clase CustomDataSetObservable también para la variable mDataSetObservable en CustomAdapterClass , como esta:

 public class CustomDataSetObservable extends Observable { public void notifyChanged() { synchronized(mObservers) { // since onChanged() is implemented by the app, it could do anything, including // removing itself from {@link mObservers} - and that could cause problems if // an iterator is used on the ArrayList {@link mObservers}. // to avoid such problems, just march thru the list in the reverse order. for (int i = mObservers.size() - 1; i >= 0; i--) { mObservers.get(i).onChanged(); } } } public void notifyInvalidated() { synchronized (mObservers) { for (int i = mObservers.size() - 1; i >= 0; i--) { mObservers.get(i).onInvalidated(); } } } public void notifyItemChanged(int position) { synchronized(mObservers) { // since onChanged() is implemented by the app, it could do anything, including // removing itself from {@link mObservers} - and that could cause problems if // an iterator is used on the ArrayList {@link mObservers}. // to avoid such problems, just march thru the list in the reverse order. mObservers.get(position).onChanged(); } } } 

en la clase CustomBaseAdapter hay un método notifyItemChanged(int position) , y puedes llamar a ese método cuando quieras actualizar una fila donde quieras (desde el botón clic o en cualquier lugar que quieras llamar a ese método). Y ¡listo !, tu única fila se actualizará al instante …