ListView no actualiza elementos ya visibles

Estoy mostrando una lista de contactos (nombre + imagen) usando ListView . Con el fin de hacer la carga inicial rápida, sólo carga los nombres en primer lugar, y aplazar la carga de la imagen. Ahora, cada vez que mi hilo de fondo acaba de cargar una imagen, programar notifyDataSetChanged() mi adaptador para ser llamado en el hilo de interfaz de usuario. Desafortunadamente, cuando esto sucede, el ListView no vuelve a renderizar (es decir, llamar a getView() para) los elementos que ya están en pantalla. Debido a esto, el usuario no ve la imagen recién cargada, a menos que se desplace y regrese al mismo conjunto de elementos, para que las vistas se reciclen. Algunos bits de código relevantes:

 private final Map<Long, Bitmap> avatars = new HashMap<Long, Bitmap>(); // this is called *on the UI thread* by the background thread @Override public void onAvatarLoaded(long contactId, Bitmap avatar) { avatars.put(requestCode, avatar); notifyDataSetChanged(); } @Override public View getView(int position, View convertView, ViewGroup parent) { // snip... final Bitmap avatar = avatars.get(contact.id); if (avatar != null) { tag.avatar.setImageBitmap(avatar); tag.avatar.setVisibility(View.VISIBLE); tag.defaultAvatar.setVisibility(View.GONE); } else { tag.avatar.setVisibility(View.GONE); tag.defaultAvatar.setVisibility(View.VISIBLE); if (!avatars.containsKey(contact.id)) { avatars.put(contact.id, null); // schedule the picture to be loaded avatarLoader.addContact(contact.id, contact.id); } } } 

AFAICT, si asume que notifyDataSetChanged() hace que los elementos en pantalla se vuelvan a crear, mi código es correcto. Sin embargo, parece que no es cierto, o tal vez me estoy perdiendo algo. ¿Cómo puedo hacer este trabajo sin problemas?

Aquí voy respondiendo a mi propia pregunta con un hackaround que he establecido. Aparentemente, notifyDataSetChanged() sólo debe utilizarse si está agregando / eliminando elementos. Si está actualizando información sobre elementos que ya están mostrados, puede terminar con elementos visibles que no actualizan su aspecto visual ( getView() no se llama a su adaptador).

Además, llamar a invalidateViews() en ListView no parece funcionar como se anuncia. Todavía consigo el mismo comportamiento glitchy con getView() no siendo llamado para actualizar artículos en pantalla.

Al principio pensé que el problema fue causado por la frecuencia con la que llamé notifyDataSetChanged() / invalidateViews() (muy rápido, debido a las actualizaciones procedentes de diferentes fuentes). Así que he intentado estrangular llamadas a estos métodos, pero aún no sirve.

Todavía no estoy 100% seguro de que esto es culpa de la plataforma, pero el hecho de que mi hackaround funciona parece sugerirlo. Por lo tanto, sin más preámbulos, mi hackaround consiste en extender el ListView para actualizar los elementos visibles. Tenga en cuenta que esto sólo funciona si está utilizando correctamente el convertView en su adaptador y nunca volver una nueva View cuando se pasó un convertView . Por obvias razones:

 public class ProperListView extends ListView { private static final String TAG = ProperListView.class.getName(); @SuppressWarnings("unused") public ProperListView(Context context) { super(context); } @SuppressWarnings("unused") public ProperListView(Context context, AttributeSet attrs) { super(context, attrs); } @SuppressWarnings("unused") public ProperListView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); } class AdapterDataSetObserver extends DataSetObserver { @Override public void onChanged() { super.onChanged(); refreshVisibleViews(); } @Override public void onInvalidated() { super.onInvalidated(); refreshVisibleViews(); } } private DataSetObserver mDataSetObserver = new AdapterDataSetObserver(); private Adapter mAdapter; @Override public void setAdapter(ListAdapter adapter) { super.setAdapter(adapter); if (mAdapter != null) { mAdapter.unregisterDataSetObserver(mDataSetObserver); } mAdapter = adapter; mAdapter.registerDataSetObserver(mDataSetObserver); } void refreshVisibleViews() { 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.v(TAG, "Refreshing view (data=" + dataPosition + ",child=" + childPosition + ")"); mAdapter.getView(dataPosition, getChildAt(childPosition), this); } } } } } 

Agregue la línea siguiente a onResume () listview.setAdapter(listview.getAdapter());

  • Detención de un subproceso dentro de un servicio
  • ¿Cómo puedo asegurar que el controlador de otro hilo no sea nulo antes de llamarlo?
  • Cómo pasar datos de la clase de hilo independiente a la actividad en Android
  • Arquitectura de aplicaciones de Android: basada en eventos o en capas
  • ¿Cómo puedo implementar un Timer / TimerTask que ejecuta un AsyncTask? (Androide)
  • Android - Dibujo fuera de pantalla de un hilo sin interfaz de usuario
  • ¿Cómo implementar la función .get con FutureTask o BackgroundTask usando android?
  • Android: la mejor práctica para realizar operaciones asíncronas en getView ()
  • Android: notifyDataSetChanged () no actualizar listview después de cambiar la orientación
  • Pasar variables entre el renderizador y otra clase con queueEvent ()
  • Picasso Imagen para Android cargando - modelo de hilo
  • FlipAndroid es un fan de Google para Android, Todo sobre Android Phones, Android Wear, Android Dev y Aplicaciones para Android Aplicaciones.