androidandroid-listviewbaseadapterandroid-lru-cache

Android BaseAdapter With LruCache Some ui problems


I am trying to implement to a list view with all contact images with the help of base adapter and LruCache. But on a long scroll on screen all the images(corresponding to that view) are displayed before setting actual image.

eg: list view with 5 items per page, if we scrolled from first contact to 60th, on first view of list view images of 1,6,11,16,21..51 are displayed for a few milli seconds before the 55th images is shown

Main codes are

//Adapter
public View getView(int position, View convertView, ViewGroup parent) {
ImageView imageView = (ImageView) convertView;
if(imageView == null){
 imageView = new ImageView(getActivity());
}    
int id =  contactId[position];
final String imageKey = String.valueOf(contactId);
final Bitmap bitmap = cache.get(imageKey);
if (bitmap != null) {
  imageView.setImageBitmap(bitmap);
} else {    
  Resources res = context.getResources();
  BitmapManager bm = new BitmapManager(imageView, res, cache);
  bm.setContext(getActivity());
  bm.execute(id);
}
return imageView;
}

BitmapManager Post Execute Code

@Override
protected void onPostExecute(Bitmap bitmap) {
    // TODO Auto-generated method stub
    try{
        if(isCancelled()){
            bitmap = null;
        }
        if(imageViewReference != null && bitmap != null){
            ImageView imageView = imageViewReference.get();
            imageView.setImageBitmap(bitmap);
            cache.put(String.valueOf(res), bitmap);
            if(imageView != null){
                imageView.setImageBitmap(bitmap);
            }

        }
    }catch(Exception e){

    }
    super.onPostExecute(bitmap);
}

How to solve this problem. Thanks


Solution

  • As you scroll down, your views get reused for new list positions that come into view. Since you start a new BitmapManager task each time getView is called, the tasks get lined up, waiting to update the image. As they each finish loading their bitmap, they put it into the ImageView in order, which is what you are seeing.

    It looks like you were trying to use a reference to the ImageView to avoid using the bitmap after it was scrolled out of view, but the reason that didn't work is that the adapter is recycling your ImageViews, and so the reference stays valid, even though in reality the ImageView is being used for a different list item now.

    There are different ways to solve this, but the simplest that comes to mind is to construct your BitmapManager with a list index, rather than the ImageView itself. Then in getView you can keep a map of what views are being used on what positions. When BitmapManager finishes, check if there is a current ImageView for the position that you've just loaded into the cache. If not, then do nothing.

    Here is some code that shows what I'm talking about. I didn't try it, so apologies if there are errors.

    //Adapter
    private SparseArray<ImageView> ivMap = new SparseArray<ImageView>();
    public View getView(int position, View convertView, ViewGroup parent) {
        ImageView imageView = (ImageView) convertView;
        if(imageView == null){
          imageView = new ImageView(getActivity());
        } else {
          // If recycled, remove the ImageView's previous position from map
          int oldPosition = ivMap.indexOfValue(imageView);
          if (oldPosition >= 0) {
            ivMap.remove(oldPosition);
          }
        }
        // Keep track of which view is representing this position
        ivMap.put(position, imageView);
    
        int id =  contactId[position];
        final String imageKey = String.valueOf(contactId);
        final Bitmap bitmap = cache.get(imageKey);
        if (bitmap != null) {
          imageView.setImageBitmap(bitmap);
        } else {    
          Resources res = context.getResources();
          BitmapManager bm = new BitmapManager(ivMap, position, res, cache);
          bm.setContext(getActivity());
          bm.execute(id);
        }
        return imageView;
    }
    
    //BitmapManager
    @Override
    protected void onPostExecute(Bitmap bitmap) {
        // TODO Auto-generated method stub
        try{
            if(isCancelled()){
                bitmap = null;
            }
            if(bitmap != null){
                cache.put(String.valueOf(res), bitmap);
                ImageView imageView = ivMap.get(position);
                if (imageView != null) {
                    imageView.setImageBitmap(bitmap);
                }
            }
        }catch(Exception e){
    
        }
        super.onPostExecute(bitmap);
    }