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?
- Cómo deshacerse de la cámara se congela (SurfaceView)?
- Android - perfil de un hilo específico (UI Thread) a través de DDMS
- Android BLE: onCharacteristicRead () parece estar bloqueado por subprocesos
- Matando un hilo en java android
- Componentes de arquitectura de Android subprocesos de red
- Asyntask: ¿por qué actualizar el hilo de la interfaz de usuario en doInbackground se encontrará con el error?
- Compartir recursos entre el contexto de OpenGL en Android
- Voleadas asincrónicas en android SyncAdapter
- "Hilo ya iniciado" al reanudar la actividad
- Cómo detener el lanzamiento de un hilo hecho con postDelayed
- Para forzar la cancelación AsyncTask no debería el indicador periódicamente marcado en doInBackground ser volátil?
- Android volátil no funciona?
- ¿Cómo comprobar FileLock sin truncar el archivo?
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());
- Acelerómetro (acceso rápido) mediante NativeActivity NDK
- Configuración de cookies en WebView android