recyclerview - Android: las imágenes visibles de ListView parpadean cuando se agregan datos a ArrayAdapter
listview desplegable android (4)
Tengo un adaptador personalizado que extiende ArrayAdapter, implementa los patrones del titular de la vista para mostrar datos (texto + imagen) de un servicio web.
para la carga lenta de las imágenes utilizo el patrón de tareas asíncronas de la capacitación avanzada en el sitio de desarrolladores de Android,
También uso disk + memoria caché de mapa de bits ram.
cuando hay datos adicionales para recuperar, agrego una vista de pie de página que al hacer clic en ella recupera datos adicionales del servicio web y lo agrega al adaptador.
el problema es que cuando se agregan estos datos nuevos, algunas de las imágenes visibles cambian e inmediatamente cambian hacia atrás, lo que resulta en un extraño parpadeo.
Aparte de eso, todo está funcionando bien y el desplazamiento es suave.
Por lo que yo entiendo, esos cambios de imagen están sucediendo cuando las vistas visibles se actualizan cuando se agregan nuevos datos.
¿Hay alguna manera de eludir este comportamiento no deseado?
esta es la clase que realiza la descarga y administra las tareas asincrónicas
public class ImageDownloader {
private ImageCache mCache;
private int reqWidth;
private int reqHeight;
public void download(String url, ImageView imageView, ImageCache imageCache, int reqHeight, int reqWidth) {
mCache = imageCache;
this.reqHeight = reqHeight;
this.reqWidth = reqWidth;
if (cancelPotentialDownload(url, imageView)) {
BitmapDownloaderTask task = new BitmapDownloaderTask(imageView);
DownloadedDrawable downloadedDrawable = new DownloadedDrawable(task);
imageView.setImageDrawable(downloadedDrawable);
task.execute(url);
}
}
private class BitmapDownloaderTask extends AsyncTask<String, Void, Bitmap> {
private String url;
private WeakReference<ImageView> imageViewReference;
public BitmapDownloaderTask(ImageView imageView) {
imageViewReference = new WeakReference<ImageView>(imageView);
}
@Override
protected Bitmap doInBackground(String... strings) {
Bitmap bitmap = null;
try {
bitmap = mCache.getBitmapFromURL(strings[0], reqWidth, reqHeight);
} catch (IOException e) {
}
return bitmap;
}
@Override
protected void onPostExecute(Bitmap bitmap) {
if (isCancelled()) bitmap = null;
if (imageViewReference != null) {
ImageView imageView = imageViewReference.get();
BitmapDownloaderTask bitmapDownloaderTask = getBitmapDownloaderTask(imageView);
if (imageView != null) {
imageView.setImageBitmap(bitmap);
}
}
}
}
private static class DownloadedDrawable extends ColorDrawable {
private WeakReference<BitmapDownloaderTask> bitmapDownloaderTaskReference;
public DownloadedDrawable(BitmapDownloaderTask bitmapDownloaderTask) {
bitmapDownloaderTaskReference = new WeakReference<BitmapDownloaderTask>(bitmapDownloaderTask);
}
public BitmapDownloaderTask getBitmapDownloaderTask() {
return bitmapDownloaderTaskReference.get();
}
}
private static BitmapDownloaderTask getBitmapDownloaderTask(ImageView imageView) {
if (imageView != null) {
Drawable drawable = imageView.getDrawable();
if (drawable instanceof DownloadedDrawable) {
DownloadedDrawable downloadedDrawable = (DownloadedDrawable)drawable;
return downloadedDrawable.getBitmapDownloaderTask();
}
}
return null;
}
private static boolean cancelPotentialDownload(String url, ImageView imageView) {
BitmapDownloaderTask bitmapDownloaderTask = getBitmapDownloaderTask(imageView);
if (bitmapDownloaderTask != null) {
String bitmapUrl = bitmapDownloaderTask.url;
if ((bitmapUrl == null) || (!bitmapUrl.equals(url))) {
bitmapDownloaderTask.cancel(true);
} else {
return false;
}
}
return true;
}
}
este es el adaptador:
la clase privada PlaceAdapter amplía ArrayAdapter {final int viewResourceId;
public PlaceAdapter(Context context, int textViewResourceId, List<PlaceModel> objects) {
super(context, textViewResourceId, objects);
viewResourceId = textViewResourceId;
}
@Override
public View getView(final int position, View convertView, ViewGroup parent) {
ViewHolder holder;
if (convertView == null) {
LayoutInflater inflater = getLayoutInflater();
convertView = inflater.inflate(viewResourceId, null);
holder = new ViewHolder(convertView);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
PlaceModel place = getItem(position);
holder.name.setText(place.getName());
holder.address.setText(place.getVicinity());
holder.position = position;
if (place.getIcon() != null) {
String url = mImageViewUrls.get(holder.image);
if (url == null || (url != null && !url.equals(place.getIcon()))) {
mDownloader.download(place.getIcon(), holder.image, mCache, 100, 100);
mImageViewUrls.put(holder.image, place.getIcon());
}
} else {
holder.image.setImageBitmap(null);
mImageViewUrls.remove(holder.image);
}
return convertView;
}
}
private class ViewHolder {
public final ImageView image;
public final TextView name;
public final TextView address;
public int position;
public ViewHolder(View row) {
image = (ImageView) row.findViewById(R.id.placeRow_imageView);
name = (TextView) row.findViewById(R.id.placeRow_placeName);
address = (TextView) row.findViewById(R.id.placeRow_placeAddress);
}
}
mImageViewUrls
es un WeakHashMap<ImageView, String>
que se correlaciona entre un ImageView
y un url, por lo que las invocaciones de tareas asincrónicas redundantes se pueden reducir al verificar si el ImageView
ya muestra la imagen requerida. sin esta implementación, el parpadeo está ocurriendo en todas las imágenes visibles en el cambio de datos. con esto, sucede solo con algunas imágenes.
EDITAR: Intenté eliminar las posibles causas de este problema, primero traté de eludir por completo la implementación de la caché y descargar cada mapa de bits de la red, luego intenté envolver mi adaptador con el adaptador Endless de CommonsWare y con ambos obtuve el mismo resultado ... esto deja solo la clase ImageDownloader y mi adaptador como posibles causas ... Estoy completamente perdido en esto.
Editar: tuve un problema similar, las imágenes solían cambiar rápidamente. Esto sucede porque
El método getView () del adaptador de lista se llama varias veces. cada vez que se llama a getView (), intenta descargar y establecer la imagen en la fila. Para cuando la imagen se descargue de la red, la fila en la lista solicitada para esa imagen podría haberse movido fuera de la porción visible de la pantalla pero el adaptador intenta reutilizar esa fila y esto lleva a configurar la imagen solicitada previamente en una nueva fila en esa posición (posición de fila previamente solicitada).
Pruebe este enfoque en el adaptador, configure la URL solicitada en ImageView con setTag:
if(item.get_referenceImage().length()!=0){
//with view, hold the url. This is used to verify that right image is loaded on download completion
holder.refImageView.setTag(item.get_referenceImage());
imageManager.loadImage(new MetaItem(item.get_referenceImage(), holder.refImageView));
}else{
//no image, set default
Log.d(TAG, "No images found for the view setting default image");
holder.refImageView.setTag(null); //this is req, otherwise image cache will retain previous image
holder.refImageView.setImageResource(R.drawable.blank_96_1382x);
}
en caché, antes de cargar el mapa de bits, verifique si se carga la imagen correcta:
String url = (String) m_imageViewRef.get().getTag();
if(url != null && url.compareTo(m_metaItem.m_url) == 0 ){
m_metaItem.m_imageViewRef.get().setImageBitmap(result);
}
PD: MetaItem es solo un POJO para mantener la referencia a imageView y la URL de la imagen
El motivo de este parpadeo es que, en la lista de lista, los elementos se vuelven a usar. Cuando se vuelven a utilizar, las vistas de la imagen en el elemento de la lista conservan la referencia de la imagen anterior que se muestra primero. Más tarde, una vez que se descarga una nueva imagen, comienza a mostrarse. esto causa el comportamiento parpadeante. Para evitar este problema de parpadeo, siempre borre la referencia de la imagen anterior de la vista de la imagen cuando se vuelva a utilizar.
En su caso, agregue holder.image.setImageBitmap (null); after holder = (ViewHolder) convertView.getTag ();
Entonces, su método getView () se verá así:
@Override public View getView (posición int final, View convertView, ViewGroup parent) {
...
if (convertView == null) {
LayoutInflater inflater = getLayoutInflater();
convertView = inflater.inflate(viewResourceId, null);
holder = new ViewHolder(convertView);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
holder.image.setImageBitmap(null)
}
...
return convertView;
}
La solución es no volver a cargar su imagen cuando no haya cambiado.
En sus adaptadores, getView () hace:
// schedule rendering:
final String path = ... (set path here);
if (holder.lastImageUrl == null || !holder.lastImageUrl.equals(path)
|| holder.headerImageView.getDrawable() == null) {
// refresh image
imageLoader.displayImage(imageUri, imageAware);
} else {
// do nothing, image did not change and does not need to be updated
}
en caso de éxito (agregue un ImageLoadingListener) establezca holder.lastImageUrl = ruta, en caso de error y cancele la configuración de holder.lastImageUrl en null para que se vuelva a cargar la próxima vez.
Es posible que esté llamando a adapter.notifyDatasetChanged()
que hace que la vista de lista vuelva a cargar sus elementos de lista. intente no llamar a adapter.notifyDatasetChanged()
su vista de lista se actualizará automáticamente cuando se desplace. pero esto puede causar la excepción ArrayIndexOutOfBounds
mientras se desplaza.