I have a Gallery, which has an adapter connects to it. In the getView method, i have a custom layout, so that I can i have image and caption displayed together
The image is downloaded from an URL, which is done asynchously, and working as expected.
Currently i make each item to fill the screen, so i only have one item display at a time, basically i want to make it like a slide show.
let me be clear, currently i have an activity, and it only has one View, which is a Gallery.
problem occurs when I am swiping, the image bounces and stays at the same image. i need to swipe many times, hard, and long swipe, then i can get to next image.
i put a debug message in my custom adapter in the getView(), it seems getView is getting called many times (4 times), and position being passed is either the current position or the previous one, which explains why i am stuck at the same screen.
if i remove the remote downloading image part, or just use a static image form the phone, i don't have any more issues, in fact, the getView only gets called once, with correct position.
i am very frustrated, not sure what the problem is, could it be because i am downloading image asynchously, which will cause the image to update which causes getView to get called again to redraw itself?
i am not sure..
please help
Unfortunately this is a bug with gallery. Listviews will scroll nicely regardless of data being updated asynchronously. However, the gallery is just not coded up to par with the listview
When the gallery tries to update a visible view (due to your image loading callback) this view will "snap" back to the focused position. If you are changing the view in any way when it is scrolling it will snap. This is likely why you have to scroll hard to get away from the current view. It is trying to perform a callback on your view and only scrolling fast will prevent the callback from occurring before you move away from that view.
I've reported this bug a while ago here:
Android Issues
There are a few workarounds posted in there you can try if you are set on using a Gallery.
Unfortunately it hasn't gained attention from the Android developers.
It seems the issue is caused with views being set to "wrap_content" and the gallery having to remeasure/redraw its views
I have since migrated away from using the gallery and instead use a ViewPager. It is much easier to manage and you don't have to worry about this problem. This has been a known problem with the Gallery since the gallery was first introduced. I have no idea if this was fixed in any of the newer Android versions (3.x/4.x). As of 2.3.7 it is not fixed.
At first you should note: Any AdapterView (Gallery, ListView etc) doesn't guarantee that each time getView() method is called it will pass the same View instance parameter. It only guarantees that each view will have the same type (see Adapter.getIntemViewType() method docs)
So, when you start image downloading you should only specify position of the element. Then after the image has been downloaded you should bind specified ImageView with downloaded image in Adapter.getView() method call.
Take a look to the ImageDownloader from the Android samples here
The other approach is to use WeakHashMap in order to contain map of adapter views to its positions. I can provide you with code samples if you need.
Related
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.
Background
I'm using the PinterestLikeAdapterView library to show some images from the internet, which is like a gridView but with different height for each cell.
The problem
Since I use this library to show images from the internet, it's crucial that when calling notifyDatasetChanged won't cause a mess on the views.
For some reason, calling this function would call the getView() method with different positions for the views. for example, even though i didn't scroll at all, and call notifyDatasetChanged (or addAll in case it's an ArrayAdapter), for position 0 it will take what was the view of position 8, for position 1 it will take the view of position 7 , and so on...
This makes the whole grid to refresh its images, and so it ruins the UX.
Usually, in both gridView and listView, the way to overcome refreshing is to put the position that was used for the view inside the viewHolder, and if they are equal, it means that they still match.
for example:
... getView(...)
{
//<=inflate a new view if needed
//avoid refreshing view in case it's still the same position:
if(position==holder.position)
return rootView;
holder.position=position;
//<=update the view according to its data
...
}
However, here they re-use other views in a different order so this trick won't work here.
Because of this issue, not only i get refreshes of almost all of the visible views, but since i use DiskCacheLru library, it crashes since it tries to put 2 identical inputSteam data into the same key using 2 threads.
The question
What can I do?
Is this a known bug in the library?
Maybe I'm using a bad way to overcome refreshes?
for now, i use memory cache to at least get items that were cached before, but that's more like a "cure" than a "vaccine"...
Short answer:
Use an image loading library like Picasso that caches most recently used images in memory, so they don't need to be reloaded from the network.
Long answer:
AdapterView does something called View recycling, where Views which are no longer needed to display a position are re-used to display another. (For example, as you scroll down, Views that disappear off the top of the screen are reused for new positions at the bottom of the screen.) Because of this, it's normal for getView() to be passed the same View for more than one position.
This is done for performance reasons: Inflating new Views is hard and takes time, so AdapterView tries to do it as infrequently as possible.
When using a holder, you store references to ImageView and TextView children inside the item's View, so you don't have to look them up with findViewById() each time - you don't usually store anything specific to a particular position, because the View and its holder will often be used for different positions.
Now, when you call notifyDataSetChanged(), AdapterView assumes that the data set has completely changed. The image that was associated with position 8 may no longer be present, or it may be associated with position 12 now. Consequently, all the existing Views are scrapped - but because AdapterView would still like to avoid inflating new Views, they're re-used to display the new data, with no regard for what position they were displaying previously.
This explains why getView() is being passed the same View for different positions, and why visible positions are being refreshed when you call notifyDataSetChanged(). But how to avoid having your images refresh, ruining the user experience?
Use an image loading library like Picasso that caches most recently used images in memory, so they don't need to be reloaded from the network. The refresh will still happen, but it'll be instantaneous.
View getView(int position, View view, ViewGroup parent) will be always called ascendingly, after notifyDataSetChanged().
I guess that, the order of finishing download task will cause this problem.
As you mentioned in your question, keeping the position is a good way to avoid this problem.
Here is another way to solve it, also re-use the imageviews.
Keep a weak reference of each ImageView in download task.
Then wrap the download task in a dummy ColorDrawable.
When getView is called, set the dummy ColorDrawable to ImageView, and start the download. When download is complete, set the downloaded image back to the referenced ImageView in OnPostExecute().
Explanation
http://android-developers.blogspot.jp/2010/07/multithreading-for-performance.html
Source code
https://code.google.com/p/android-imagedownloader/source/checkout
There is a very good example on PinterestLikeListView in GitHub
Here is the library StaggeredGridView
A modified version of Android's experimental StaggeredGridView. Includes own OnItemClickListener and OnItemLongClickListener, selector, and fixed position restore.
You can get library project here library
and you can get Demo project Here
This is very good open source project, so you can use instead of PinterestLikeAdapterView
Hope this library is going to help you out.
seems that the authors of this library have fixed it, after some time i've reported about it:
https://github.com/huewu/PinterestLikeAdapterView/issues/8
This is not a big issue, but it looks strange. In a list or gridview I load images with a delay (e.g. loaded from remote place). I'm recycling the items, using convertView. When I scroll down fast, I see the old images repeated, until the correct images are fetched and replace them.
Is there a way to change this? I tried, for example, at the very start of getView() (after initializing convertView, if necessary), to set the imageView visibility to GONE. And set to VISIBLE after the image has been fetched. But for some reason, this doesn't work (still looks the same).
Thanks!
Edit: I'm not even sure if this is normal behaviour when using recycled views, or if I'm doing something wrong. It doesn't look like a bug when the internet connection is fast, or when fetching from the file system. Then the new images are loaded very fast and the user doesn't see repeated items. But on a slow internet connection, it looks like a bug.
Before setting image in row just check if the image belong to the content of view.
I had the same problem sometime back and I had to change my image downloader so that it takes image url as well as meta data so that once download is complete, i can compare meta-data with view's data.
Hi geeks!
Here is a problem using ListView.
When generating a single list item view, in the method of BaseAdapter.getView, some continuous async remote data request may be issued, like getting the photo of a person.
But when the response is back, the list item view might be reused, as the mechanism of ListView works. I cannot find the right view to put the response to.
How did you guys fix this problem?
As you say, there is a good chance that by the time you've loaded the image that the list item is no longer visible or even existing. So you can check what is on screen using ListView.getFirstVisiblePosition() and ListView.getLastVisiblePosition(), if it is onScreen get the item and set the image. Otherwise you have to cache the image (either memory or sd-card depending on how many images and the size of them) and next time getView() is called for that item just use the cached image.
I hope that helps.
I believe what your after is "lazy image loading" + caching. I got my implementation going via this answer. The package is called LazyList.
I hope this helps
I have a gallery and when a image is selected, I want to start new thread (for loading other images). But I don't want to do it while the gallery is scrolling. How can I know the state of the gallery and if it is still scrolling?
I can't use "setCallbackDuringFling(false)" because I have a textview under the image that has to be updated during the scrolling...
Thanks!
Marc
I think you could have a go with setCallbackDuringFling method. From what I understand, if you set it to false, it shouldnt be possible to select an item while fling is being computed.
Use a Handler that is called from the onItemSelectedListener that updates the last time that an item was selected (every time the foremost item changes this listener is fired) and use a timeout value to determine when scrolling ended to determine if scrolling is still occurring.
However, I have the exact same thing in andAMP... I have a FrameLayout with a Gallery and 2 textviews to show the foremost selected Artist and Album... I use onItemSelectedListener to update those TextViews and it works fine. If you have the TextViews in a FrameLayout like me you have to use bringChildToFront to get them to show over the Gallery (add the Gallery last).
As far as Lazy Loading (which I am assuming that is what you are doing). There are a couple of great tutorials about how to do this.
http://ballardhack.wordpress.com/2010/04/05/loading-remote-images-in-a-listview-on-android/
http://android-developers.blogspot.com/2010/07/multithreading-for-performance.html