Using AsyncTask to load Images in ListView

55,544

Solution 1

You can send in the ImageView to the task constructor and keep a reference to the image path there. Now at onPostExecute, check if the current tag of the ImageView is the same as the one that you started with. If yes, then set the image. If no, don't do anything.

However, this means that the image will be downloaded in any case. You'll just not set the wrong image on the view.

EDIT: First pass the ImageView to the task constructor:

new LoadImage(imageView).execute()

Then save a reference to the ImageView and image path in LoadImage constructor. It is important to save the path in the constructor and not in doInBackground to ensure that we don't run into multi threading problems. Then at onPostExecute we check the current path.

class LoadImage extends AsyncTask<Object, Void, Bitmap>{

        private ImageView imv;
        private String path;

        public LoadImage(ImageView imv) {
             this.imv = imv;
             this.path = imv.getTag().toString();
        }

    @Override
    protected Bitmap doInBackground(Object... params) {
        Bitmap bitmap = null;
        File file = new File( 
                Environment.getExternalStorageDirectory().getAbsolutePath() + path);

        if(file.exists()){
            bitmap = BitmapFactory.decodeFile(file.getAbsolutePath());
        }

        return bitmap;
    }
    @Override
    protected void onPostExecute(Bitmap result) {
        if (!imv.getTag().toString().equals(path)) {
               /* The path is not same. This means that this
                  image view is handled by some other async task. 
                  We don't do anything and return. */
               return;
        }

        if(result != null && imv != null){
            imv.setVisibility(View.VISIBLE);
            imv.setImageBitmap(result);
        }else{
            imv.setVisibility(View.GONE);
        }
    }

}

Solution 2

This Android Developers Blog post will give you a complete reference project for this complete with caching. Just replace the Http access code with SD card file reads.

Solution 3

I hope this helps. After lot of search I have this working solution.

public class CustomAdapter extends ArrayAdapter<String>{
    /*
    public CustomAdapter(Context context , String[] video) {
        super(context,R.layout.custom_row, video);
    }
*/

private final Activity context;
private final String[] video;

static class ViewHolder {
    public TextView videoTitle;
    public ImageView videoThumbnail;
    public int position;
    public String path;
}

public CustomAdapter(Activity context, String[] video) {
    super(context, R.layout.custom_row, video);
    this.context = context;
    this.video = video;
}
@Override
public View getView(final int position, View convertView, ViewGroup parent) {

    LayoutInflater videoInflator = LayoutInflater.from(getContext());
    View customView = videoInflator.inflate(R.layout.custom_row, parent, false);
        ViewHolder viewHolder = new ViewHolder();
        viewHolder.position = position;
        viewHolder.path = video[position];
        viewHolder.videoTitle = (TextView) customView.findViewById(R.id.videoTitle);
        viewHolder.videoThumbnail = (ImageView) customView.findViewById(R.id.videoThumbnail);
        //rowView.setTag(viewHolder);
    //}
    customView.setTag(viewHolder);


    final String videoItem = video[position];
    int index=videoItem.lastIndexOf('/');
    String lastString=(videoItem.substring(index +1));
    index = lastString.indexOf(".mp4");
    lastString=(lastString.substring(0,index));
    viewHolder.videoTitle.setText(lastString);

    new AsyncTask<ViewHolder, Void, Bitmap>() {
        private ViewHolder v;

        @Override
        protected Bitmap doInBackground(ViewHolder... params) {
            v = params[0];
            Bitmap thumb = ThumbnailUtils.createVideoThumbnail(videoItem, MediaStore.Images.Thumbnails.MINI_KIND);
            return thumb;
        }

        @Override
        protected void onPostExecute(Bitmap result) {
            super.onPostExecute(result);
            if (v.position == position) {
                // If this item hasn't been recycled already, hide the
                // progress and set and show the image
                v.videoThumbnail.setImageBitmap(result);
            }
        }
    }.execute(viewHolder);
    return customView;
}

}

Solution 4

Maybe you should try:

view = mInflater.inflate(mResource,parent,null);

Check this blog it explains the similar issue:

http://www.doubleencore.com/2013/05/layout-inflation-as-intended/

Share:
55,544
Rodrigo
Author by

Rodrigo

Ruby On Rails Programmer

Updated on May 31, 2020

Comments

  • Rodrigo
    Rodrigo almost 4 years

    I have one ListView which can hold an image. It depends if image exists or not in SDCARD.

    Here my example code:

    public class MainActivity extends Activity  {
    
        ListView mListView;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
    
            mListView = new ListView(this);
            setContentView(mListView);
    
            String[] arr = new String[] { 
                    "/example/images/1.jpg", "/example/images/2.jpg",  
                    "/example/images/3.jpg", "/example/images/4.jpg",  
                    "/example/images/5.jpg", "/example/images/6.jpg", 
                    "/example/images/7.jpg", "/example/images/8.jpg",  
                    "/example/images/9.jpg", "/example/images/1.jpg", 
                    "/example/images/2.jpg", "/example/images/3.jpg",  
                    "/example/images/4.jpg", "/example/images/5.jpg",  
                    "/example/images/6.jpg", "/example/images/7.jpg",  
                    "/example/images/8.jpg", "/example/images/9.jpg", 
                    "/example/images/1.jpg", "/example/images/2.jpg",  
                    "/example/images/3.jpg", "/example/images/4.jpg",  
                    "/example/images/5.jpg", "/example/images/6.jpg", 
                    "/example/images/7.jpg", "/example/images/8.jpg",  
                    "/example/images/9.jpg", "/example/images/1.jpg", 
                    "/example/images/2.jpg", "/example/images/3.jpg",  
                    "/example/images/4.jpg", "/example/images/5.jpg",  
                    "/example/images/6.jpg", "/example/images/7.jpg",  
                    "/example/images/8.jpg", "/example/images/9.jpg"}; 
    
            List<String> list = Arrays.asList(arr);
    
            MyAdapter adapter = new MyAdapter(this, R.layout.listitem_imv, list);
    
            mListView.setAdapter(adapter);
        }
    
        class MyAdapter extends ArrayAdapter<String>{
    
            List<String> mList;
            LayoutInflater mInflater;
            int mResource;
    
            public MyAdapter(Context context, int resource,
                    List<String> objects) {
                super(context, resource, objects);
    
                mResource = resource;
                mInflater = getLayoutInflater();
                mList = objects;
            }
    
            @Override
            public View getView(int position, View convertView, ViewGroup parent) {
                View view;
    
                if(convertView == null){
                    view = mInflater.inflate(mResource, null);
                }else{
                    view = convertView;
                }
    
                ImageView imageView = (ImageView) view.findViewById(R.id.imv);
                TextView textView = (TextView) view.findViewById(R.id.txv);
    
                                imageView.setTag(mList.get(position));//tag of imageView == path to image
                new LoadImage().execute(imageView);
                textView.setText(mList.get(position).toString());
    
                return view;
            }       
        }
    
        class LoadImage extends AsyncTask<Object, Void, Bitmap>{
    
            private ImageView imv;
            private String path;
    
    
            @Override
            protected Bitmap doInBackground(Object... params) {
                imv = (ImageView)   params[0];
    
                path = imv.getTag().toString();
    
                Bitmap bitmap = null;
                File file = new File( 
                        Environment.getExternalStorageDirectory().getAbsolutePath() + path);
    
                if(file.exists()){
                    bitmap = BitmapFactory.decodeFile(file.getAbsolutePath());
                }
    
                return bitmap;
            }
            @Override
            protected void onPostExecute(Bitmap result) {
                if(result != null && imv != null){
                    imv.setVisibility(View.VISIBLE);
                    imv.setImageBitmap(result);
                }else{
                    imv.setVisibility(View.GONE);
                }
            }
        }
    }
    

    The 'sdcard/example/images' directory has the images: 1.jpg, 2.jpg, 3.jpg, 4.jpg, 6.jpg, 7.jpg and 9.jpg. the expected result is: example

    But, if I scroll the list quickly, some images are inserted in the wrong items. It happens due to use of convertView in getView() method.

    If I use the following code, the code works fine:

            //if(convertView == null){
            //  view = mInflater.inflate(mResource, null);
            //}else{
            //  view = convertView;
            //}
            view = mInflater.inflate(mResource, null);
    

    When list scrolled quickly, two asyncTasks can reference one same View, due to use of convertView. How Can I cancel AsyncTask when the View is no longer visible?(and is useb by another item of ListView)

    edit

                @Override
        protected void onPostExecute(Bitmap result) {
            if(result != null && imv != null){
    
                if(imv.getTag().equals(path)){
                    imv.setVisibility(View.VISIBLE);
                    imv.setImageBitmap(result);
                }else{
                    imv.setVisibility(View.GONE);
                }
    
            }else{
                imv.setVisibility(View.GONE);
            }
        }
    
  • Rodrigo
    Rodrigo over 12 years
    I tried this. see edit. But images are still being put in wrong items.
  • Blundell
    Blundell almost 12 years
    @Rodrigo Also if you cache the image you download before you check if it is the correct one, it means it's not a wasted download as it can be used by your other tasks
  • Rodrigo
    Rodrigo about 11 years
    Yes @Droider, but look at post date.
  • Didac Perez Parera
    Didac Perez Parera over 10 years
    @shabyasachi this solution is not correct, I do not understand the upvotes. The ListView does not work like this, it uses always the same view items for displaying the content, and when scrolling, it uses recycled views for display the new items. That solution is WRONG AT ALL.
  • shabyasachi
    shabyasachi almost 10 years
    @DídacPérez : Check the big comment in onPostExecute. That comment explains how view recycling is handled.
  • TPG
    TPG over 9 years
    Got it working.. Thanks, now the listview loaded faster.
  • Salman Khakwani
    Salman Khakwani over 7 years
    In case you are using reusable Views, you can take a WeakReference to your ImageView and check the Tag on the ImageView before loading Bitmaps into it. I hope this helps anyone who is using Reusable Views.