Images are repeated in ListView

I implemented an Android application that should download images from the server and display them in a ListView, but a very interesting thing occurs when loading images

As you can see in videos that have not yet been uploaded, those that are already uploaded are presented. How can this happen? I have been thinking about this for almost two days.

http://www.youtube.com/watch?v=lxY-HAuJO0o&feature=youtu.be

here is my ListView adapter code.

public class MoviesAdapter extends ArrayAdapter<ParkCinema> { private ArrayList<ParkCinema> movieDataItems; private Activity context; public MoviesAdapter(Activity context, int textViewResourceId, ArrayList<ParkCinema> movieDataItems) { super(context, textViewResourceId, movieDataItems); this.context = context; this.movieDataItems = movieDataItems; } @Override public View getView(int position, View convertView, ViewGroup parent) { if (convertView == null) { LayoutInflater vi = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); convertView = vi.inflate(R.layout.movie_data_row, null); } ParkCinema movie = movieDataItems.get(position); if (movie!=null){ ImageView imageView = (ImageView) convertView.findViewById(R.id.movie_thumb_icon); String url = movie.poster(); if (url!=null) { Bitmap bitmap = fetchBitmapFromCache(url); if (bitmap==null) { new BitmapDownloaderTask(imageView).execute(url); } else { imageView.setImageBitmap(bitmap); } } } return convertView; } private LinkedHashMap<String, Bitmap> bitmapCache = new LinkedHashMap<String, Bitmap>(); private void addBitmapToCache(String url, Bitmap bitmap) { if (bitmap != null) { synchronized (bitmapCache) { bitmapCache.put(url, bitmap); } } } private Bitmap fetchBitmapFromCache(String url) { synchronized (bitmapCache) { final Bitmap bitmap = bitmapCache.get(url); if (bitmap != null) { return bitmap; } } return null; } private class BitmapDownloaderTask extends AsyncTask<String, Void, Bitmap> { private String url; private final WeakReference<ImageView> imageViewReference; public BitmapDownloaderTask(ImageView imageView) { imageViewReference = new WeakReference<ImageView>(imageView); } @Override protected Bitmap doInBackground (String... source) { url = source[0]; Bitmap image; try{ image = BitmapFactory.decodeStream(new URL(url).openConnection().getInputStream()); return image; } catch(Exception e){Log.e("Error", e.getMessage()); e.printStackTrace();} return null; } @Override protected void onPostExecute(Bitmap bitmap) { addBitmapToCache(url, bitmap); imageViewReference.get().setImageBitmap(bitmap); } } } 

Edit 3:

 public View getView(int position, View convertView, ViewGroup parent) { if (convertView == null) { LayoutInflater vi = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); convertView = vi.inflate(R.layout.movie_data_row, null); } ParkCinema movie = movieDataItems.get(position); ImageView imageView = (ImageView) convertView.findViewById(R.id.movie_thumb_icon); if (movie!=null){ String url = movie.poster(); if (url != null) { Bitmap bitmap = fetchBitmapFromCache(url); if (bitmap == null) { imageView.setImageResource(R.drawable.no_image); new BitmapDownloaderTask(imageView).execute(url); } else { imageView.setImageBitmap(bitmap); } } else { imageView.setImageResource(R.drawable.no_image); } } else { imageView.setImageResource(R.drawable.no_image); } return convertView; } 
+6
source share
6 answers

Yeah! I think I know this problem. Right now, your getView method sets your ImageView as follows:

  • Returns the movie object in position
  • Displays the thumbnail URL of the movie
  • Using this url, it tries to find the image in cache
  • If he finds an image, he sets it
  • If he cannot find the image, he runs an async network request to retrieve it, and installs it after downloading it.

Your issus arises because ListView reuses' View s strings. When the first View scrolls from the screen, and does not inflate a new one, the ListView passes the current off-screen View row as a convertView for reuse (this is for efficiency).

When your getView gets a convertView that is reused, its ImageView has already been installed from the row that had it before, so you see the old image from the off-screen View row. Using the current getView process getView you check for the presence of a new row image and do not find it in the cache, it starts a request to download it. While you are loading, you see the old image until you get a new image.

To fix this, you need to make sure that you immediately set each field in the View line to make sure you don't have a View displaying obsolete data. I suggest you install ImageView on the drawable resource by default (you set it in your R.layout.movie_data_row ) while you wait for the network to load to receive the image.

 @Override public View getView(int position, View convertView, ViewGroup parent) { if (convertView == null) { LayoutInflater vi = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); convertView = vi.inflate(R.layout.movie_data_row, null); } ParkCinema movie = movieDataItems.get(position); ImageView imageView = (ImageView) convertView.findViewById(R.id.movie_thumb_icon); if (movie != null) { String url = movie.poster(); if (url != null) { Bitmap bitmap = fetchBitmapFromCache(url); if (bitmap == null) { // Set the movie thumbnail to the default icon while we load // the real image imageView.setImageResource(R.drawable.movie_thumb_icon); new BitmapDownloaderTask(imageView).execute(url); } else { // Set the image to the bitmap we get from the cache imageView.setImageBitmap(bitmap); } } else { // Set the movie thumbnail to the default icon, since it doesn't // have a thumbnail URL imageView.setImageResource(R.drawable.movie_thumb_icon); } } else { // Set the movie thumbnail to the default icon, since there no // movie data for this row imageView.setImageResource(R.drawable.movie_thumb_icon); } 

-Edit -

Updated to be even more robust using drawable . You also have a problem with your BitmapDownloaderTask , it does not handle / null errors. Try adding this as well.

 @Override protected void onPostExecute(Bitmap bitmap) { addBitmapToCache(url, bitmap); if (bitmap == null) { // Set the movie thumbnail to the default icon, since an error occurred while downloading imageViewReference.get().setImageResource(R.drawable.movie_thumb_icon); } else { imageViewReference.get().setImageBitmap(bitmap); } } 
+19
source

I had this problem and implemented lruCache ... I believe that you need api 12 and above or use the v4 compatibility library. lurCache is fast memory, but it also has a budget, so if you are worried about it, you can use diskcache ... it's all described here http://developer.android.com/training/displaying-bitmaps/cache- bitmap.html

Now I will provide my implementation, which is called oneton, I call from where like this:

// where is the line first and the other is the image to load

 DownloadImageTask.getInstance().loadBitmap(avatarURL, iv_avatar); 

here is the ideal code for caching, and then called above in the getView adapter when retrieving the web image:

  public class DownloadImageTask { private LruCache<String, Bitmap> mMemoryCache; /* create a singleton class to call this from multiple classes */ private static DownloadImageTask instance = null; public static DownloadImageTask getInstance() { if (instance == null) { instance = new DownloadImageTask(); } return instance; } //lock the constructor from public instances private DownloadImageTask() { // Get max available VM memory, exceeding this amount will throw an // OutOfMemory exception. Stored in kilobytes as LruCache takes an // int in its constructor. final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024); // Use 1/8th of the available memory for this memory cache. final int cacheSize = maxMemory / 8; mMemoryCache = new LruCache<String, Bitmap>(cacheSize) { @Override protected int sizeOf(String key, Bitmap bitmap) { // The cache size will be measured in kilobytes rather than // number of items. return bitmap.getByteCount() / 1024; } }; } public void loadBitmap(String avatarURL, ImageView imageView) { final String imageKey = String.valueOf(avatarURL); final Bitmap bitmap = getBitmapFromMemCache(imageKey); if (bitmap != null) { imageView.setImageBitmap(bitmap); } else { imageView.setImageResource(R.drawable.ic_launcher); new DownloadImageTaskViaWeb(imageView).execute(avatarURL); } } private void addBitmapToMemoryCache(String key, Bitmap bitmap) { if (getBitmapFromMemCache(key) == null) { mMemoryCache.put(key, bitmap); } } private Bitmap getBitmapFromMemCache(String key) { return mMemoryCache.get(key); } /* a background process that opens a http stream and decodes a web image. */ class DownloadImageTaskViaWeb extends AsyncTask<String, Void, Bitmap> { ImageView bmImage; public DownloadImageTaskViaWeb(ImageView bmImage) { this.bmImage = bmImage; } protected Bitmap doInBackground(String... urls) { String urldisplay = urls[0]; Bitmap mIcon = null; try { InputStream in = new java.net.URL(urldisplay).openStream(); mIcon = BitmapFactory.decodeStream(in); } catch (Exception e) { Log.e("Error", e.getMessage()); e.printStackTrace(); } addBitmapToMemoryCache(String.valueOf(urldisplay), mIcon); return mIcon; } /* after decoding we update the view on the mainUI */ protected void onPostExecute(Bitmap result) { bmImage.setImageBitmap(result); } } 

}

+1
source

Views are reused to work with adapters. You should use a different approach. You must have a class owner who reuses your views. In your case, the class should be something like this:

  public class MoviesAdapter extends ArrayAdapter<ParkCinema> { private ArrayList<ParkCinema> movieDataItems; private Activity context; public MoviesAdapter(Activity context, int textViewResourceId, ArrayList<ParkCinema> movieDataItems) { super(context, textViewResourceId, movieDataItems); this.context = context; this.movieDataItems = movieDataItems; } @Override public View getView(int position, View convertView, ViewGroup parent) { if (convertView == null) { LayoutInflater vi = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); convertView = vi.inflate(R.layout.movie_data_row, null); holder = new ViewHolder(); holder.imageView = (BarImageView) convertView.findViewById(R.id.movie_thumb_icon); } else { holder = (ViewHolder) convertView.getTag(); } ParkCinema movie = movieDataItems.get(position); if (movie!=null){ String url = movie.poster(); if (url!=null) { Bitmap bitmap = fetchBitmapFromCache(url); if (bitmap==null) { new BitmapDownloaderTask(imageView).execute(url); } else { imageView.setImageBitmap(bitmap); } } } return convertView; } private LinkedHashMap<String, Bitmap> bitmapCache = new LinkedHashMap<String, Bitmap>(); private void addBitmapToCache(String url, Bitmap bitmap) { if (bitmap != null) { synchronized (bitmapCache) { bitmapCache.put(url, bitmap); } } } private Bitmap fetchBitmapFromCache(String url) { synchronized (bitmapCache) { final Bitmap bitmap = bitmapCache.get(url); if (bitmap != null) { return bitmap; } } return null; public static class ViewHolder { ImageView imageView; } } 
0
source

I spent hours trying to figure it out too ... Thanks to Steven Bile's decision ... Here is my solution to something similar when a user selects an item from a list:

 adapter.setSelectedIndex(position); 

then in the user adapter:

 public void setSelectedIndex(int ind) { selectedIndex = ind; notifyDataSetChanged(); } 

and then finally in the getView method of the adapter:

 if(selectedIndex!= -1 && position == selectedIndex) { holder.tab.setBackgroundColor(Color.BLACK); } else{ holder.tab.setBackgroundColor(Color.DKGRAY); } 

So, at the end, make sure you assign default values

0
source

In my case, I used the Picasso library instead of AsyncTask to load the image. enter the link here

Also write if the else condition is set to null for the image if the URL is unavailable

0
source

instead of using the convertview object, a new view is created each time.

 View localView = ((LayoutInflater)parentscreen.getSystemService("layout_inflater")).inflate(R.layout.activity_list_row, null); 

Heating as above.

-2
source

All Articles