问题
I would like to create an deferred loading adapter for use with a Gallery
widget.
That is to say getView()
returns an ImageView
immediately, and later some other mechanism will asynchronously call its setImageBitmap()
method. I did this by creating a "lazy" ImageView
that extends ImageView
.
public class GalleryImageView extends ImageView {
// ... other stuff here ...
public void setImage(final Looper looper, final int position) {
final Uri uri = looper.get(position);
final String path = looper.sharePath(position);
new Thread(new Runnable() {
@Override
public void run() {
GalleryBitmap gbmp = new GalleryBitmap(context, uri, path);
final Bitmap bmp = gbmp.getBitmap(); // all the work is here
handler.post(new Runnable() {
@Override
public void run() {
if (GalleryImageView.this.getTag().equals(uri)) {
setImageBitmap(bmp);
}
}
});
}
}).start();
}
}
When I scroll slowly in the Gallery
, the center image keeps popping into the center. It's hard to explain, exactly, but it's really annoying. I also tried the same approach for a spinner adapter and it works perfectly there.
Any ideas?
回答1:
The solution is to implement a more intelligent method of when to fetch thumbnails - it is pointless fetching thumbnails while the user is flinging through the list. Essentially you want something like that implemented in Romain Guy's Shelves application.
To get the most responsive Gallery you'll need to implement some form of in-memory cache and do the following:
- Only set an image if it exists in the in-memory cache from your
getView
. Set a flag indicating whether the image was set or whether a download is required. You could also maintain a memory in a cache on the SD card and internal memory, and if a fling is not currently ongoing then show a low res (inSampleSize
set to 16 or 8) version which will be visible when just scrolling through - the high res version will load when the user lets go and settles on an image. - Add an
OnItemSelectedListener
(and make sure to callsetCallbackDuringFling(false)
when initializing) that downloads new thumbnails for all the visible items that require a download only if the users finger is up (you can usegetFirstVisiblePosition
andgetLastVisiblePosition
to find the range of views visible) - Also when the user lifts their finger check to see 1. if the selected position changed since the user put their finger down and if so 2. whether a download was initiated due to your
OnItemSelectedListener
- if it wasn't then initiate one. This is to catch the case where no flinging occurs, and thusOnItemSelected
never does anything because it is always called with the finger down in this situation. I'd use a Handler to delay starting the downloading by the animation time of your gallery (make sure to clear any delayed messages posted to this handler wheneveronItemSelected
is called or when you get anACTION_DOWN
event. - After an image is downloaded check if any visible views requested this image then and update those views
Also be aware that the default Gallery component does not properly implement View recycling (it assumes each position in the adapter has a unique view, and also clears the recycler of these items when they go offscreen making it pretty pointless). Edit: on more looking it isn't pointless - but it's not a recycler in terms of next/previous views, rather it serves to avoid having to call getView
for the current views during layout changes.
This means the convertView
parameter passed to your getView
method will more often that not be null, meaning you'll be inflating a lot of views (which is expensive) - see my answer to Does a replacement for Gallery with View recycling exist? for some hints on that. (PS: I have since modified that code - I would use a different recycle bin for layout phases and scroll phases, in the layout phase place and retrieve the views in the layout recycle bin according to their position, and DO NOT call getView if the view you get from the bin is non-null since it will be exactly the same view; also clear the layout recycle bin after the layout phase -- this makes things a bit more snappier)
PS: Also be very careful with what you do in OnItemSelected
- namely unless it's in the places mentioned above then try to do as little as possible. For instance I was setting some text in a TextView
above my Gallery in OnItemSelected
. Just moving this call into the same points as where I updated thumbnails made a noticable difference.
回答2:
I have an answer for you!
When any of the setImage...
methods are called on ImageView
in internally a layout pass is requested, for example, setImageBitmap()
as above is defined as such
public void setImageBitmap(Bitmap bm) {
setImageDrawable(new BitmapDrawable(mContext.getResources(), bm));
}
which calls
public void setImageDrawable(Drawable drawable) {
if (mDrawable != drawable) {
mResource = 0;
mUri = null;
updateDrawable(drawable);
requestLayout(); //layout requested here!
invalidate();
}
}
which has the effect of the gallery 'snapping' to the center of the image thats currently closest to the center of the gallery.
What I have done to prevent this is have the View thats loading into the Gallery have a explicit height and width (in dip
s) and using an ImageView
subclass that ignores layout requests. This works as the gallery still has a layout pass initially but does not bother doing this every time an image in the gallery changes, which I imagine would only need to happen if the gallery views had their width and height set to WRAP_CONTENT
, which we dont. Note that as invalidate()
is still called in setImageDrawable()
the image will still be drawn when set.
My very simple ImageView
subclass below!
/**
* This class is useful when loading images (say via a url or file cache) into
* ImageView that are contained in dynamic views (Gallerys and ListViews for
* example) The width and height should be set explicitly instead of using
* wrap_content as any wrapping of content will not be triggered by the image
* drawable or bitmap being set (which is normal behaviour for an ImageView)
*
*/
public class ImageViewNoLayoutRefresh extends ImageView
{
public ImageViewNoLayoutRefresh(Context context, AttributeSet attrs, int defStyle)
{
super(context, attrs, defStyle);
}
public ImageViewNoLayoutRefresh(Context context, AttributeSet attrs)
{
super(context, attrs);
}
public ImageViewNoLayoutRefresh(Context context)
{
super(context);
}
@Override
public void requestLayout()
{
// do nothing - for this to work well this image view should have its dims
// set explicitly
}
}
edit: i should mention that the onItemSelected approaches can also work, but as I needed to hook into that while flinging was taking place I came up with the above, which I think is more flexible approach
回答3:
This might be a bug in the Gallery's onLayout method. Check out http://code.google.com/p/android/issues/detail?id=16171 for a possible workaround.
来源:https://stackoverflow.com/questions/5758406/android-gallery-view-stutters-with-deferred-image-loading-adapter