Android: ListView visible images flicker when adding data to ArrayAdapter

I have a custom adapter that extends the ArrayAdapter, it implements view owner templates to display data (text + image) from a web service.

for lazy loading of images I use the asynchronous tasks template from the advanced training on the Android developers site,

I also use cache with + ram.

when there is additional data to retrieve. I add a footer view that clicks on it, extracts additional data from the web service, and adds it to the adapter.

The problem is that when you add this new data, some of the visible images change and immediately change back, which leads to a strange flicker.

except that everything works fine, and the scrolling is smooth.

As I understand it, these image changes occur when visible views are updated when new data is added.

Is there any way around this unwanted behavior?

this is a class that loads and manages async tasks

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; } 

}

this is the adapter:

private class PlaceAdapter extends 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 is a WeakHashMap<ImageView, String> that appears between the ImageView and the URL, so redundant calls to asynchronous tasks can be reduced if you check that ImageView already showing the desired image. without this implementation, flicker occurs in all visible images when data changes. with this, this only happens with some images.

EDIT: I tried to eliminate the possible causes of this problem, first I tried to completely bypass the cache implementation and download each bitmap from the network, and then tried to wrap my adapter using the CommonsWare Endless adapter, and from both I got the same result .. so this leaves only the ImageDownloader class and my adapter as possible reasons. I completely lost it.

+8
android android-listview android-asynctask android-adapter cwac-endless
source share
4 answers

The reason for this flicker is that the items in the list list are reused. When reusing an image, the old link to the image is retained in the list item. Later, when a new image is loaded, it starts showing. this causes flickering behavior. To avoid this flashing problem, always clear the old image link from the image when reusing it.

In your case add holder.image.setImageBitmap (null); after owner = (ViewHolder) convertView.getTag ();

So your getView () method will look like this:

@Override public View getView (end position int, View convertView, parent ViewGroup) {

 ... 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; 

}

+4
source share

Edit: I had a similar problem, the images used to quickly change. This is because

The getView () method of the List adapter is called several times. every time getView () is called, you are trying to load and set the image in a row. By the time the image is downloaded from the network, the line in the list that is requested for this image can be deleted from the visible part of the screen, but the adapter tries to reuse this line, and this leads to the installation of the previously requested image in a new line at this position ( previously requested row position).

Try this approach in the adapter, set the required URL in ImageView using 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); } 

in the cache, before loading the bitmap, check if the correct image is loaded:

  String url = (String) m_imageViewRef.get().getTag(); if(url != null && url.compareTo(m_metaItem.m_url) == 0 ){ m_metaItem.m_imageViewRef.get().setImageBitmap(result); } 

PS: MetaItem is just a POJO for linking to ImageView and image URL

0
source share

The solution is to not reload the image when it has not changed.

In your getView () do adapters:

 // 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 } 

If it succeeds (add ImageLoadingListener), you set the holder.lastImageUrl = path file, if it fails, cancel the installation of holder.lastImageUrl to null so that it reboots next time.

0
source share

Perhaps you are calling adapter.notifyDatasetChanged() , which causes listview reload its lists. try not to call adapter.notifyDatasetChanged() your list should automatically update when scrolling. but when scrolling this can throw an ArrayIndexOutOfBounds exception.

0
source share

All Articles