para - programacion android studio pdf




ListView no actualiza los elementos ya visibles (2)

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

Estoy mostrando una lista de contactos (nombre + imagen) usando el ListView . Para hacer que la carga inicial sea rápida, solo cargo los nombres primero, y diferir la carga de la imagen. Ahora, cada vez que mi hilo de fondo termina de cargar una imagen, programa la notifyDataSetChanged() mi adaptador para que se llame en el hilo de la interfaz de usuario. Desafortunadamente, cuando esto sucede, ListView no vuelve a renderizar (es decir, llama 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 vuelva al mismo conjunto de elementos, para que las vistas se reciclen. Algunos bits relevantes de código:

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 esté perdiendo algo. ¿Cómo puedo hacer que esto funcione sin problemas?


Aquí voy respondiendo mi propia pregunta con un hackaround que me he establecido. Aparentemente, notifyDataSetChanged() solo debe usarse si está agregando / eliminando elementos. Si está actualizando la información sobre los elementos que ya se muestran, puede terminar con elementos visibles que no actualicen su apariencia visual getView() no se llame a getView() en su adaptador).

Además, parece que llamar a invalidateViews() en el ListView no funciona como se anuncia. Sigo teniendo el mismo comportamiento getView() no se llama a getView() para actualizar los elementos en pantalla.

Al principio, pensé que el problema se debía a la frecuencia con la que llamé a notifyDataSetChanged() / invalidateViews() (muy rápido, debido a las actualizaciones provenientes de diferentes fuentes). Así que he intentado limitar las llamadas a estos métodos, pero aún no he tenido éxito.

Todavía no estoy seguro al 100% de que esto sea culpa de la plataforma, pero el hecho de que mi piratería funcione parece sugerir que sí. Así que, sin más preámbulos, mi solución consiste en extender el ListView para actualizar los elementos visibles. Tenga en cuenta que esto solo funciona si está utilizando correctamente el convertView en su adaptador y nunca devuelve una nueva View cuando se aprobó 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);
                }
            }
        }
    }

}




android-listview