I have a listView with an adapter loading images from URLs through the ImageLoader class. The problem is that the images on the screen are not displayed / loaded until the list item is scrolled.
So basically, a listView is displayed, but the images do not load until you scroll down and scroll back. This applies to all items in the list when they are displayed the first time you load images, until you go to them.
I myself did not write the ImageLoader class, and itβs hard for me to understand why the images do not load on first display. I tried many different things with the queue, but nothing helps.
ImageLoader Class:
public class ImageLoader { //the simplest in-memory cache implementation. This should be replaced with something like SoftReference or BitmapOptions.inPurgeable(since 1.6) private HashMap<String, Bitmap> cache=new HashMap<String, Bitmap>(); private File cacheDir; public ImageLoader(Context context){ //Make the background thead low priority. This way it will not affect the UI performance photoLoaderThread.setPriority(Thread.NORM_PRIORITY-1); //Find the dir to save cached images if (android.os.Environment.getExternalStorageState().equals(android.os.Environment.MEDIA_MOUNTED)) cacheDir=new File(android.os.Environment.getExternalStorageDirectory(),"LazyList"); else cacheDir=context.getCacheDir(); if(!cacheDir.exists()) cacheDir.mkdirs(); } final int stub_id=R.drawable.nopic; public void DisplayImage(String url, Activity activity, ImageView imageView, ProgressBar progressBar) { if(cache.containsKey(url)){ imageView.setImageBitmap(cache.get(url)); if (progressBar != null){ progressBar.setVisibility(View.GONE); imageView.setVisibility(View.VISIBLE); } }else{ queuePhoto(url, activity, imageView, progressBar); if(progressBar != null){ imageView.setVisibility(View.GONE); //ADDED progressBar.setVisibility(View.VISIBLE); } else { imageView.setImageResource(stub_id); } } } private void queuePhoto(String url, Activity activity, ImageView imageView, ProgressBar progressBar) { //This ImageView may be used for other images before. So there may be some old tasks in the queue. We need to discard them. photosQueue.Clean(imageView); PhotoToLoad p = new PhotoToLoad(url, imageView, progressBar); synchronized(photosQueue.photosToLoad){ photosQueue.photosToLoad.push(p); photosQueue.photosToLoad.notifyAll(); } //start thread if it not started yet if(photoLoaderThread.getState()==Thread.State.NEW) photoLoaderThread.start(); } private Bitmap getBitmap(String url) { //I identify images by hashcode. Not a perfect solution, good for the demo. String filename=String.valueOf(url.hashCode()); File f=new File(cacheDir, filename); //from SD cache Bitmap b = decodeFile(f); if(b!=null) return b; //from web try { Bitmap bitmap=null; InputStream is=new URL(url).openStream(); OutputStream os = new FileOutputStream(f); Utils.CopyStream(is, os); os.close(); bitmap = decodeFile(f); return bitmap; } catch (Exception ex){ ex.printStackTrace(); return null; } } //decodes image and scales it to reduce memory consumption private Bitmap decodeFile(File f){ try { //decode image size BitmapFactory.Options o = new BitmapFactory.Options(); o.inJustDecodeBounds = true; BitmapFactory.decodeStream(new FileInputStream(f),null,o); //Find the correct scale value. It should be the power of 2. int requiredSize = 100; int width_tmp=o.outWidth, height_tmp=o.outHeight; int scale=1; while(true){ if(width_tmp/2<requiredSize || height_tmp/2<requiredSize) break; width_tmp/=2; height_tmp/=2; scale*=2; } //decode with inSampleSize BitmapFactory.Options o2 = new BitmapFactory.Options(); o2.inSampleSize=scale; return BitmapFactory.decodeStream(new FileInputStream(f), null, o2); } catch (FileNotFoundException e) {} return null; } //Task for the queue private class PhotoToLoad { public String url; public ImageView imageView; public ProgressBar progressBar; public PhotoToLoad(String u, ImageView i, ProgressBar p){ url=u; imageView=i; progressBar = p; } } PhotosQueue photosQueue=new PhotosQueue(); public void stopThread(){ photoLoaderThread.interrupt(); } //stores list of photos to download class PhotosQueue { private Stack<PhotoToLoad> photosToLoad=new Stack<PhotoToLoad>(); //removes all instances of this ImageView public void Clean(ImageView image) { for(int j=0 ;j<photosToLoad.size();){ if(photosToLoad.get(j).imageView==image) photosToLoad.remove(j); else ++j; } } } //XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX // PHOTOSLOADER: Thread //XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX class PhotosLoader extends Thread { public void run() { try { while(true) { //thread waits until there are any images to load in the queue if(photosQueue.photosToLoad.size() == 0) synchronized(photosQueue.photosToLoad){ photosQueue.photosToLoad.wait(); } if(photosQueue.photosToLoad.size() != 0) { PhotoToLoad photoToLoad; synchronized(photosQueue.photosToLoad){ photoToLoad = photosQueue.photosToLoad.pop(); photosQueue.photosToLoad.remove(photoToLoad); } Bitmap bmp = getBitmap(photoToLoad.url); cache.put(photoToLoad.url, bmp); Object tag = photoToLoad.imageView.getTag(); if(tag != null && ((String)tag).equals(photoToLoad.url)){ BitmapDisplayer bd = new BitmapDisplayer(bmp, photoToLoad.imageView, photoToLoad.progressBar); Activity a = (Activity)photoToLoad.imageView.getContext(); a.runOnUiThread(bd); } } if(Thread.interrupted()) break; } } catch (InterruptedException e) { //allow thread to exit } } } PhotosLoader photoLoaderThread = new PhotosLoader(); //XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX // BITMAP DISPLAYER //XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX //Used to display bitmap in the UI thread class BitmapDisplayer implements Runnable{ Bitmap bitmap; ImageView imageView; ProgressBar progressBar; public BitmapDisplayer(Bitmap b, ImageView i, ProgressBar p){ bitmap = b; imageView = i; progressBar = p; } public void run(){ if(bitmap != null){ imageView.setImageBitmap(bitmap); if (progressBar != null){ imageView.setVisibility(View.VISIBLE); progressBar.setVisibility(View.GONE); } }else{ if (progressBar != null){ progressBar.setVisibility(View.VISIBLE); imageView.setVisibility(View.GONE); } } } } public void clearCache() { //clear memory cache cache.clear(); //clear SD cache File[] files = cacheDir.listFiles(); for(File f:files) f.delete(); } }
Adapter:
public class Adapter_Agenda extends BaseAdapter { // DEBUG private final String TAG = this.getClass().getSimpleName(); private LayoutInflater inflater = null; public ViewHolder holder; private ArrayList<Agenda> agendas; UnixTimeToStringConverter unixConverter; ImageLoader imageLoader; View vi; public Adapter_Agenda(Context context, ArrayList<Agenda> agendas) { this.agendas = agendas; unixConverter = new UnixTimeToStringConverter(); imageLoader = new ImageLoader(context); inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); } public int getCount() { return agendas.size(); } @Override public Agenda getItem(int position) { return agendas.get(position); } public long getItemId(int position) { return position; } public View getView(int position, View convertView, ViewGroup parent) { vi = convertView; if(convertView == null){ vi = inflater.inflate(R.layout.item_list_agenda, null); holder = new ViewHolder(); holder.header = (TextView)vi.findViewById(R.id.item_list_header); holder.image = (ImageView)vi.findViewById(R.id.item_list_image); holder.time = (TextView)vi.findViewById(R.id.item_list_time); holder.description = (TextView)vi.findViewById(R.id.item_list_description); vi.setTag(holder); holder = (ViewHolder)vi.getTag(); } else { holder = (ViewHolder)vi.getTag(); } holder.header.setText(agendas.get(position).getHeader()); imageLoader.DisplayImage(agendas.get(position).getImageURL(), activity, holder.image, null); holder.time.setText(unixConverter.getUnixDateFormated(agendas.get(position).getStartTime())); return vi; } //XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX // VIEWHOLDER //XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX public class ViewHolder{ public TextView time; public ImageView image; public TextView header; public TextView description; } }