I've a list of URLs, each corresponding to an image.
I need to display these images in a ListView.
Currently, my approach is pass this List<String> containing URLs to a CustomAdapter (extending BaseAdapater).
Each row item in the ListView will be inflated with an ImageView.
Then in the getView() method, if currentImageView == null, then I'll start an AsyncTask, which will download the bitmap for that imageList.get(position).
Then in the postExecute(), I'll set currentImageView.setBitmap(downloadedBitmap).
In this manner,from inside the adapter, I'll call AsyncTask one-by-one for each image.
If you think, there is something wrong in this approach then please let me know. Also, please suggest the correct approach.
One pitfall with approach I've found in when AsyncTask for a certain list element is in progress & user scrolls down then back up, the same async task will be called again.
How can I prevent this?
When your views get recycled, the currentImageView will not be null and it will keep displaying the same images. I would suggest using Universal Image Loader. It takes care of all of that for you, with TONS of extra features like caching and what not.
Try this library
Then in getView method, simply add this code
UrlImageViewHelper.setUrlDrawable(holder.image, model.getimageURL(), R.drawable.placeholder);
parameter one is your ImageView, second parameter is Image URL, and the last is placeholder image.
Related
TL,DR : How can I force the recycler view to call the onBindViewHolder method again, at least for the visible items?
Calling notifyDataSetChanged() will make the list lag for some milliseconds, is there a better way?
Thanks.
I have a layout with an ImageView.
When ever bind is called for the imageview, I send a request to a server in order to get an image.
When the image is loaded, I save the bitmap in the ViewHolder , T variable.
And in the bind method I check if variable.getBitmap() is null or not, and if it is I will set the imageview.
Now If I scroll my list the images are going to be loaded, but If not, the imageviews are still blank, because onBindViewHolder wasn't called again.
Thanks.
notifyDataSetChanged() is definitely the right way. Maybe notifyItemChanged() is even better because it only binds the selected item. If I get your question right, it seems that you do some things on the Main Thread which shouldn't be done there. Remember: Never do potential lengthy actions on the Main Thread, but always use something like AsyncTask.
I think it is a better approach to download the images asynchronously and then cache it, so you don't have to download it every time your Views are recycled. There are libraries for it.
While the images are loading you could show a ProgressBar or something else which signalizes the user, that the image is being loaded.
I have a class that extends BaseAdapter, which I use for a ListView. In the getView() method of that class I'm using an AsyncTask with a callback method to download an image (once downloaded, I store it, so I don't have to download it again). When the ListView loads the items first, only the first item displays an image an then starts to change the images (repeatedly displaying the images of the other items). Later also the other items start showing the same behaviour. After a while they stop cycling the images and each item shows the correct images. If I scroll the ListView the items are starting again to cycle the images.
This only happens, if I recycle the convertView parameter, that is passed to getView(). If I don't the images take very long to show up, and I'm afraid I'm creating too much new Views.
Thanks in advance for any advices, to get this working properly.
The simple way to get this going is to dump all your own image loading code and use something that already handles this, like Square's Picasso. There are plenty of alternatives if you do not like Picasso.
Otherwise, your tasks will need to be aware of the recycling, so they do not attempt to update an ImageView that now needs a different image. For example, you could use setTag() and getTag() on the ImageView to store (and retrieve) the URL the ImageView currently needs, and if the image downloaded by the task is some other URL, don't apply it.
But, again, please use an existing library for this. It's 2013: few people should be rolling their own download-the-images-for-use-in-ListView-rows code anymore.
I am working on an app which requires an autocompletetextview for suggesting contacts in dropdown view. I am using a custom cursor adapter. My layout for one drop down item has imageview on the left (for showing image of the contact if available, else a default image).
I have used Asynctask to load images from the contacts. In my bindView method, I have following snippet to load images from the Async task.
contactImage = new conactImage(viewHolder, cursor);
contactImage.execute(new String[] {userId});
and in the Asynctask's onPostExecute method, I set the image bitmap.
mViewHolder.cImage.setImageBitmap(imageResultBitmap);
mViewHolder is the viewholder passed as argument in the previous snippet.
The problem I am facing is a bad UX. Suppose I entered two characters in the autocompletetextview. The images show up in a second. When I scroll, the images don't change unless I wait another 2 3 seconds to get the correct image loaded. And when I scroll fast, the views flicker a lot because of the random image bitmap changes.
I understand it is a view recycling thing, but this gets annoying. Please suggest any other possible way to do the same.
on scrolling
Thanks!
Let's assume i got a list of ImageView and TextView with LinearLayout.
Now i'm using simple ArrayAdapter extension that gets Strig[][] as items which is an array of double strings (each entry contains String[2] where the first string is a uri to the image and the second one is the text. i override getView to display the image and text , recyling views, of course :-).
However since image loading, even from resources may be slow, i'm doing it in an async task, that means that i have a default image loaded and i use: startLoading (it's my method) that stakes the ImageView and the Uri and once the image is ready it places it in the image view.
However sometimes, since it's a list if the image is not loaded yet and I'm in the middle of the process it's worth while stopping the loading, and that's in case that the line i'm loading image for is no longer visible.
Since the views are recycled i can't check if ImageView is visible since it may be visible but with a different Drawable... is there a way for me to know at some point that my line in invalidated and not visible ? is there a simple way or shell i need to start using flags and tag pattern and stuff like that to discover that my line is no longer valid ?
What I do is based on how the ListView manages it's views.
As there is a limited set of Views really used by the ListView and that might be passed to our Adapter through the convertView argument of the getView() method, I implemented the following:
I have a LinkedList class member where I store the Uris to be loaded. These Uris are consumed in an AsyncTask member to retrieve the pics.
Before returning the item view in getView(), my Adapter places the Urin of the item pic in the View.setTag(Object).
When I reuse a convertView, before reusing it's content I get the Uri it was previously linked to using View.getTag() and remove the corresponding element from the pending Uris LinkedList.
I have a GridView, in my activity, that displays about 30 thumbnails fetched from a remote server. I am doing 2-level caching for the images:
there is an in-memory cache(HashMap using softReferences)
Secondly, all the images fetched are written to Sdcard(so as to avoid refetches when in-memory bitmaps are GC'd).
From the getView of the GridView-Adapter, I call getCachedImage function of the Cache class, which will either return a default image or an image from one of the caches. When a default image is returned, the Cache class starts a AsyncTask to fetch the image from the remote server. This image is then also written to both the caches.
The problem is: When the AsyncTask finishes, I want it to also update the UI alongwith the caches. I tried passing the instance of the GridView child(ImageView) to the AsyncTask and then setting the image of this child view, in the postExecute of the AsyncTask but due to View recycling, this approach results in incorrect images in incorrect views, if the fetches take long time.
What are the ways in which i can make my AsyncTask more proactive meaning it should let the UI know that image download has finished?
Thanks!
The approach I took with CWAC-Thumbnail is to do everything you did, but also stick the URL presently being displayed by an ImageView in that ImageView object's tag via setTag(). Then, when the AsyncTask ends, I check the tag to see if the URL of the ImageView matches the URL the task downloaded. If they do, I apply the image. If not, I don't.
A way to handle this case is to simply write a Custom Image View (which inherits of ImageView) that is able to deal with all this stuff by itself.
Then you can just put your Custom image Views wherever you need it and just call something like "startLoading" on it.
In this "startLoading", just put the logic you described.