As described in many tutorials and in Android developers pages too, I'm using an async task to load images as thumbnails in a ListView. The task loads full size image from SDcard, resize it and put it in the ImageView of list item's layout.
Everything works well, except for the fact that after scrolling list fast up & down, the image of a single visible element is updated two or three times with different images before getting the right one.
This behavior is related, in my opinion, to the recycling views in ListView: when an asynctask is ready to inject the list's element-X image in the referred view, the view itself might be already been recycled and assigned to list's element-Y.
I'm conscious about some ugliness of my code, for example the fact that I've implemented neither volatile nor persistent cache for thumbnails (targeted for next release), but the problem would be only partially hidden by that.
I found a possible solution using libraries for loading image, but I'm investigating how to fix in my code because the problem is more generally related to using async code in conjunction with list and today I deal with images, but tomorrow I'could face the same problem loading text or any other kind of data.
Possible solutions I'm investigating are:
- Inform the asynctask about the item of the list it is working for, once loaded image updates it only if the item is visible
- When list detaches the view from element (how can I detect this?), stop the asynctask
- Override list's OnScrollListener to check when OnScroll event happens if an item exits from visible items' list and the stop its asynctask, if exists.
Is one of these solutions viable or con you suggest a different one?
This is my list's adapter (I'm using an expandable list in a fragment):
@Override
public View getChildView(int groupPosition, final int childPosition, boolean isLastChild, View convertView, ViewGroup parent) {
Log.i(TAG, "ExpandableListAdapter.getChildView entered, getting view n. " + groupPosition + "-" + childPosition + ", convertview = " + convertView);
ViewHolder holder;
if (convertView == null) {
convertView = inf.inflate(R.layout.selfie_list_item_layout, parent, false);
holder = new ViewHolder();
holder.date = (TextView) convertView.findViewById(R.id.selfieListItemDateView);
holder.place = (TextView) convertView.findViewById(R.id.selfieListItemPlaceView);
holder.thumb = (ImageView) convertView.findViewById(R.id.selfieListItemThumbView);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
Integer mChildIndex = (Integer) getChild(groupPosition, childPosition);
SelfieItem mChildObj = selfies.get(mChildIndex);
String mText = mChildObj.getDate().toString();
holder.date.setText(mText);
holder.thumb.setImageBitmap(BitmapFactory.decodeResource(convertView.getResources(), R.drawable.selfie_place_holder));
File selfieFile = mChildObj.getFile();
new LoadSelfieTask(mFragmentContext).executeOnExecutor(AsyncTask.SERIAL_EXECUTOR, selfieFile, holder.thumb);
return convertView;
}
And the following is async code:
@Override
protected Bitmap doInBackground(Object... params) {
File selfieFile = (File)params[0];
Bitmap mySrcBitmap = null;
Bitmap myDestBitmap = null;
if (selfieFile.exists()) {
mySrcBitmap = BitmapFactory.decodeFile(selfieFile.getAbsolutePath());
}
if (mySrcBitmap != null) {
// Get info about view to be updated
mImageViewToBeUpdated = (ImageView) params[1];
mImageHeight = mImageViewToBeUpdated.getHeight();
mImageWidth = mImageViewToBeUpdated.getWidth();
if (mySrcBitmap.getWidth() >= mySrcBitmap.getHeight()){
myDestBitmap = Bitmap.createBitmap(
mySrcBitmap,
mySrcBitmap.getWidth()/2 - mySrcBitmap.getHeight()/2,
0,
mySrcBitmap.getHeight(),
mySrcBitmap.getHeight()
);
}else{
myDestBitmap = Bitmap.createBitmap(
mySrcBitmap,
0,
mySrcBitmap.getHeight()/2 - mySrcBitmap.getWidth()/2,
mySrcBitmap.getWidth(),
mySrcBitmap.getWidth()
);
}
mySrcBitmap = Bitmap.createScaledBitmap(myDestBitmap, mImageWidth, mImageHeight, true);
}
return mySrcBitmap;
}
Thanks in advance for your answers.