0

I have an ImageAdapter that is loading thumbnails into a gridView.

public View getView(int position, View convertView, ViewGroup parent) {
    ImageView imageView;

    if (convertView == null) {
        imageView = new ImageView(mContext);
        imageView.setLayoutParams(new GridView.LayoutParams(85, 85));
        imageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
        imageView.setPadding(8, 8, 8, 8);

        LoadThumbs downloader = new LoadThumbs(imageView);

        downloader.execute(Loggedin.mImageIds.get(position));

    } else {
        imageView = (ImageView) convertView;
    }

    return imageView;

}

mImageIds is a string ArrayList with url's of images on my server. My LoadThumbs class just downloads images in an asynctask.

public class LoadThumbs extends AsyncTask<String, Void, Bitmap> {
private String url;


private final WeakReference<ImageView> imageViewReference;    

public LoadThumbs(ImageView imageView) {
    imageViewReference = new WeakReference<ImageView>(imageView);

}

protected Bitmap doInBackground(String... params) {
    url = params[0];
    try {
        return BitmapFactory.decodeStream(new URL(url).openConnection().getInputStream());
    } catch (MalformedURLException e) {
        e.printStackTrace();
        return null;
    } catch (IOException e) {
        e.printStackTrace();
        return null;
    }
}

protected void onPostExecute(Bitmap result) {
    if (imageViewReference != null) {
        ImageView imageView = imageViewReference.get();
        if (imageView != null) {

            imageView.setImageBitmap(result);
        }
    }
}

protected void onPreExecute() {
    if (imageViewReference != null) {
        ImageView imageView = imageViewReference.get();
        if (imageView != null) {

            imageView.setImageResource(R.drawable.loadcircle);
        }
    }
}

}

It works, it downloads the images. But then when I rotate the screen the images appear in reversed order.

There were several discussions on this already, but those didn't work for me. They would either redownload all the images when rotating the device or wouldn't work at all.

I was thinking of maybe putting the images in a bytearray and when convertView != null get them out of that bytearray instead of redownloading. But I can certainly use help with this. There has to be something with the way my getView works.

Thanks

3 Answers3

0

I can see that when you use convertView - you did not call convertView.setImageBitmap() - may be this is the problem?

Jin35
  • 8,602
  • 3
  • 32
  • 52
  • My LoadThumbs class does that in the if(convertView == null) statement. It sets the images to the imageView, but when the screen rotates the children of the gridView appear in reversed order when gridView is rebuilt. – Martin van der Woude Jan 23 '12 at 13:38
  • I mean situation: 1. you create list item A + task for loading image AI and list item B + BI. When both tasks complete, you see list with two images. Now you rotate device - list view start recreating, with using old list items. Yo don't know order of convertView - you should manually set image for it (because tasks already finished) – Jin35 Jan 23 '12 at 13:43
  • Makes sense this way. This concept is totally new for me, so I'm still not sure what to do now. Do you have any code maybe or pointers on how to manually set the images and where I would have to do that? I've bugged Google for hours but no decent results. – Martin van der Woude Jan 23 '12 at 14:01
  • You have to store `Bitmap` objects somewhere, and then set it in `getView()` in `else{}` block – Jin35 Jan 23 '12 at 14:09
0

Most likely this happens because views are reused by ListView.

I'm quite sure it will work OK if you change your getView to

  if (true/*convertView == null*/) {

This way, views will not be reused.
Note, this is just for testing where the underlying cause is.
Reusing convertView is a good approach.

If this proves to be working correctly, the problem is indeed in your getView else branch as Jin35 already indicated. Also in the else branch, you should call imageView.setImageBitmap(someBitmap) because you can't assume that the convertView parameter matches the position-parameter.

Marc Van Daele
  • 2,856
  • 1
  • 26
  • 52
  • This works in the sense that it displays the images in the correct order. But, it redownloads them every time the screen rotates. If I turn 3g or wifi off and rotate the device, they don't appear anymore. Any ideas? I was thinking that I should download them all once, and after that work with the getView to get them out of a bitarray. – Martin van der Woude Jan 23 '12 at 17:04
  • you should indeed do some caching. Using https://github.com/kaeppler/droid-fu works quite well for me. See also http://brainflush.wordpress.com/2009/11/23/droid-fu-part-2-webimageview-and-webgalleryadapter/ for some more information. – Marc Van Daele Jan 23 '12 at 17:12
  • I posted an answer, it works but I feel it's not optimal. Any comments are appreciated! – Martin van der Woude Jan 23 '12 at 22:25
0

What I have now is the following:

ImageAdapter has public static ArrayList<Bitmap> bitmapArray = new ArrayList<Bitmap>();

And the getView is this:

public View getView(int position, View convertView, ViewGroup parent) {
    ImageView imageView;

    if (convertView == null) {
        imageView = new ImageView(mContext);
        imageView.setLayoutParams(new GridView.LayoutParams(85, 85));
        imageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
        imageView.setPadding(8, 8, 8, 8);

        LoadThumbs downloader = new LoadThumbs(imageView);

        downloader.execute(Loggedin.mImageIds.get(position));

    } else {
        imageView = (ImageView) convertView;
        try{
            imageView.setImageBitmap(bitmapArray.get(position+1));
        } catch (Exception e){}
    }

    return imageView;
}

And then in LoadThumbs:

protected void onPostExecute(Bitmap result) {
    if (imageViewReference != null) {
        ImageView imageView = imageViewReference.get();
        if (imageView != null) {
            ImageAdapter.bitmapArray.add(result);
            imageView.setImageBitmap(result);
        }
    }
}

What this does is it first downloads the images, puts them in the imageView. At the same time it puts them in the Bitmap array. When the screen rotates, it uses the images in the array instead of redownloading them. As far as I understand.

It uses imageView.setImageBitmap(bitmapArray.get(position+1)); (Note the +1) because for some reason the second bitmap (index 1) in the bitmapArray is a duplicate of the first (index 0).

This now works. I bet it's not the best or cleanest way of doing this, so if any of you have improvements for my code, let it know!

EDIT

For anyone trying to solve this. I ended up with a solution that now 100% works for me.

I first filled the bitmap ArrayList with empty indexes:

    for (int i = 0; i < Loggedin.mImageIds.size(); i++) { 
        ImageAdapter.bitmapArray.add(null);
    }

I then made a new Object to pass to AsyncTask instead of a string. This object holds the url and the position integer.

public View getView(int position, View convertView, ViewGroup parent) {
    ImageView imageView;

    if (convertView == null) {
        imageView = new ImageView(mContext);
        imageView.setLayoutParams(new GridView.LayoutParams(85, 85));
        imageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
        imageView.setPadding(8, 8, 8, 8);

        LoadThumbs downloader = new LoadThumbs(imageView);

        Object[] arr = new Object[2];
        arr[0] = Loggedin.mImageIds.get(position);
        arr[1] = new Integer(position);

        downloader.execute(arr);

    } else {
        imageView = (ImageView) convertView;

        try {

            imageView.setImageBitmap(bitmapArray.get(position));
        } catch(Exception e){}

    }

    return imageView;
}

In LoadThumbs I have an Object instead of the string

public class LoadThumbs extends AsyncTask<Object, Void, Bitmap> {

Then in the doInBackground method I get the correct parameters from the object.

url = (String) params[0];
position = (Integer) params[1];

In onPostExecute method I put the image in the array at the correct position:

try {
     ImageAdapter.bitmapArray.set(position, result);
} catch (Exception e) {}      

At last now the else branch in the getView method can access the correct bitmaps.

  • wrt the extra '+1': I would suggest to print out position and url in getView and in your doInBackground. That seems weird. – Marc Van Daele Jan 24 '12 at 07:25
  • Using a ArrayList as cache might be OK for your needs. But if you need to cache more images you will probably not be able anymore to cache all images so you might need something more advanced like droid-fu which has a first level cache in memory and a second level cache on the local 'disk'. I would also rather use a Map as cache structure instead of the ArrayList: it makes the cache independent of position. See also http://stackoverflow.com/questions/541966/android-how-do-i-do-a-lazy-load-of-images-in-listview – Marc Van Daele Jan 24 '12 at 07:30
  • Turned out my 'solution' wasn't 100% working. It worked alright with 3 or so images, but more than that and it still would create duplicates and wrong orders. I only need to add maybe max 20 small thumbnails to the array, so I think I'm fine. I'll look into your suggestions. Thanks – Martin van der Woude Jan 24 '12 at 16:45