Using AsyncTask to load Images in ListView
Asked Answered
U

6

15

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);
        }
    }
Undergarment answered 11/10, 2011 at 16:9 Comment(0)
L
42

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);
        }
    }

}
Lyons answered 11/10, 2011 at 18:16 Comment(7)
I tried this. see edit. But images are still being put in wrong items.Undergarment
@Undergarment 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 tasksImplacental
Yes @Droider, but look at post date.Undergarment
@Lyons 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.Abutilon
@DídacPérez : Check the big comment in onPostExecute. That comment explains how view recycling is handled.Lyons
Got it working.. Thanks, now the listview loaded faster.Enviable
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.Antichrist
C
9

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.

Candlestand answered 30/7, 2012 at 20:54 Comment(0)
T
2

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;
}

}

Thorfinn answered 30/5, 2016 at 14:49 Comment(0)
H
1

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/

Homemaker answered 3/7, 2014 at 20:22 Comment(0)
E
0

What I would do (unless you have thousands of images): 1. create a data structure - a simple class holding a String name to be displayed and a bitmap 2. create an adapter for it 3. in the getView method assign the correct bitmap to the correct ImageView.

In your case though you can create a similar data structure but holding not a bitmap but an AsyncTask. Anyway you need to bind the asynctask to the string into one item. An array (or arraylist) of such items will be fed to your adapter. Displayed will be an imageview and a textview.

AsyncTask can be cancelled with cancel().

Etesian answered 11/10, 2011 at 16:23 Comment(0)
J
0

Hey I found the solution to this problem just use following function instead of your function

 @Override
protected void onPostExecute(Bitmap result) {

    if (!imv.getTag().toString().equals(rec_id)) {
           return;
    }

    if(result != null && imv != null){
        int index = id.indexOf(imv.getTag().toString());
        if(list.getFirstVisiblePosition()<=index && index<=list.getLastVisiblePosition())
        {
            imv.setVisibility(View.VISIBLE);
            imv.setImageBitmap(result);
        }
    }else{
        imv.setImageBitmap(icon);
        imv.setVisibility(View.GONE);
    }
}

Here list is the object of listview. Just pass your list view object to your adapter and paste this function instead of your onPostExecute function.

Janellajanelle answered 9/11, 2012 at 8:44 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.