Harry Summers
Harry Summers

Reputation: 11

Loading a Bitmap thumbnail into a RecyclerView with AsyncTask bug

I am creating an app that has a file directory that contains pictures, videos, pdfs, etc. I am currently working on displaying thumbnails for pictures. I am using the RecyclerView and ViewHolder to display list items that each represent a photo item. I then use an AsyncTask to download the Bitmaps one at a time and store them in a Hashmap. Everything works fine except when I scroll down in a large list of photos very quickly. The placeholder image for random items at the bottom of the list are replaced with thumbnails that have already been loaded at the top of the list. When the background thread reaches the image at the bottom, then the correct image replaces the wrong image. After all the thumbnails are loaded then everything works as intended.

Here is the code for the AsyncTask. I think the problem has to do with the position integer I am passing into the constructor. The position variable represents the position in the Adapter. Maybe there is a way to make sure the image is loading the placeholder image I have in onPreExecute()?

/**
 * AsyncTask to download the thumbnails in the RecyclerView list.
 */

private class CreateThumbnail extends AsyncTask<Void, Void, android.graphics.Bitmap> {

    // ******
    // FIELDS
    // ******

    private ImageView mPreviewInstance;
    private File mFile;
    private RelativeLayout.LayoutParams lp;
    private FileHolder mFileHolder;
    private int mPosition;
    private UUID mId;
    private FolderFile mFolderFile;

    // ***********
    // Constructor
    // ***********

    /**
     * @param holder - ViewHolder passed for the list item.
     * @param position - position in the Adapter.
     * @param id - id for list item stored in database.
     */

    private CreateThumbnail(FileHolder holder, int position, UUID id) {
        mPosition = position;
        mFileHolder = holder;
        mPreviewInstance = mFileHolder.mImagePreview;
        mId = id;
        mFolderFile = FolderFileLab.get(getContext()).getFolderFile(mId);
    }

    // ****************
    // OVERRIDE METHODS
    // ****************

    @Override
    protected void onPreExecute() {

    }

    @Override
    protected Bitmap doInBackground(Void... params) {

        FolderFileLab lab = FolderFileLab.get(getContext());

        if (!lab.getCurrentMap().containsKey(mId)) {
            mFile = lab.getPhotoFile(mFolderFile);

            // Create Bitmap (Biggest use of memory and reason this background thread exists)
            Bitmap bitmap = PictureUtils.getScaledBitmap(
                    mFile.getPath(), getActivity());

            // Scales Bitmap down for thumbnail.
            Bitmap scaledBitmap;
            if (bitmap.getWidth() >= bitmap.getHeight()) {
                scaledBitmap = Bitmap.createBitmap(bitmap, bitmap.getWidth() / 2
                                - bitmap.getHeight() / 2,
                        0, bitmap.getHeight(), bitmap.getHeight());
            } else {
                scaledBitmap = Bitmap.createBitmap(bitmap, 0, bitmap.getHeight() / 2
                                - bitmap.getWidth() / 2,
                        bitmap.getWidth(), bitmap.getWidth());
            }

            // Cache bitmap
            HashMap<UUID, Bitmap> map = lab.getCurrentMap();
            map.put(mId, scaledBitmap);
            lab.updateMap(map);

            return scaledBitmap;
        } else {
            // If Hashmap already contains the id get the Bitmap.
            return lab.getCurrentMap().get(mId);
        }
    }

    @Override
    protected void onPostExecute(Bitmap bitmap) {

        // Checks to see if the bitmap is still displayed in the list. If not nothing happens.
        // If it is then it displays the image.
        if (mPreviewInstance.getVisibility() == View.VISIBLE && mFileHolder.getPosition()
                == mPosition && bitmap != null) {

            // Formatting for thumbnail
            lp = new RelativeLayout.LayoutParams(
                    RelativeLayout.LayoutParams.WRAP_CONTENT, RelativeLayout
                    .LayoutParams.WRAP_CONTENT);
            lp.setMargins(7, 7, 7, 0);

            // Displaying thumbnail on UI thread.
            mPreviewInstance.setLayoutParams(lp);
            mPreviewInstance.setBackground(null);
            mPreviewInstance.setImageBitmap(bitmap);
        }
    }

}

Here is some of the relevant Adapter code where the AsyncTask is started.

@Override
    public void onBindViewHolder(FileHolder holder, int position) {
        FolderFile file = mFiles.get(position);
        holder.bindFile(file);

        if (file.isPhoto()) {
            createThumbnail = new CreateThumbnail(holder, position,file.getId());
            createThumbnail.execute();
        }
    }

Upvotes: 0

Views: 2569

Answers (2)

Harry Summers
Harry Summers

Reputation: 11

Figured it out! I added code to change the photo to the placeholder image after every bind. This is what I changed in my adapter.

 @Override
    public void onBindViewHolder(FileHolder holder, int position) {
        FolderFile file = mFiles.get(position);
        holder.bindFile(file);

        if (file.isPhoto()) {
            Drawable placeholder = getResources().getDrawable(R.mipmap.picture_blu);
            holder.mImagePreview.setBackground(placeholder);
            holder.mImagePreview.setImageBitmap(null);


            createThumbnail = new CreateThumbnail(holder, position, file.getId());
            createThumbnail.execute();
        }
    }

Upvotes: 1

Francesc
Francesc

Reputation: 29260

Your views are recycled, so by the time the async task finishes, the imageView has been reused and has a new image assigned to it.

What you can do is assign to the imageView a tag that is the file name of the file you are trying to load into it. You keep track of that same file name in the async task. Then in your AsyncTask, in onPostExecute, you check if the tag the imageView has is the same file name that you just loaded. If it is, you go ahead and set the bitmap to the imageView. If it is not, then the view has been recycled and you simply drop the Bitmap you just created; another async task will be loading the right bitmap.

Upvotes: 0

Related Questions