android 画廊视图“口吃”带有延迟图像加载适配器

发布于 2024-11-02 15:03:49 字数 1145 浏览 5 评论 0原文

我想创建一个延迟加载适配器以与 Gallery 小部件一起使用。

也就是说,getView()立即返回一个ImageView,稍后其他一些机制将异步调用其setImageBitmap()方法。我通过创建一个扩展 ImageView 的“惰性”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();
}

}

当我在图库中缓慢滚动时,中心图像不断弹出到中心。确切地说,很难解释,但这确实很烦人。我还对旋转适配器尝试了相同的方法,并且它在那里完美地工作。

有什么想法吗?

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?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

赠意 2024-11-09 15:03:49

解决方案是实现一种更智能的方法来确定何时获取缩略图 - 当用户快速浏览列表时获取缩略图是毫无意义的。本质上,您希望在 Romain Guy 的 Shelves 应用程序中实现类似的功能。

要获得响应速度最快的图库,您需要实现某种形式的内存缓存并执行以下操作:

  • 仅在 getView 中设置内存缓存中存在的图像。设置一个标志,指示图像是否已设置或是否需要下载。您还可以在 SD 卡和内部存储器上的缓存中维护内存,如果当前未进行滑动,则显示低分辨率(inSampleSize 设置为 16 或 8)版本,该版本将可见当只是滚动浏览时 - 当用户放开并选择图像时,将加载高分辨率版本。
  • 添加一个 OnItemSelectedListener(并确保在初始化时调用 setCallbackDuringFling(false)),仅当用户手指向上(您可以使用 getFirstVisiblePositiongetLastVisiblePosition 来查找可见视图的范围)
  • 此外,当用户抬起时他们的手指检查 1. 自用户放下手指以来所选位置是否发生变化,如果是,2. 是否由于您的 OnItemSelectedListener 启动了下载 - 如果没有,则启动下载。这是为了捕获没有发生滑动的情况,因此 OnItemSelected 永远不会执行任何操作,因为在这种情况下总是用手指向下调用它。我将使用处理程序来延迟开始下载画廊的动画时间(请确保在调用 onItemSelected 或收到 ACTION_DOWN 时清除发布到此处理程序的任何延迟消息 另
  • 下载图像后,检查是否有任何可见视图请求该图像并更新这些视图

请注意,默认的 Gallery 组件无法正确实现视图回收(它假设适配器中的每个位置都有一个唯一的视图) ,并且还清除了这些的回收器编辑:在更多的情况下,它并不是毫无意义的 - 但它不是下一个/上一个视图的回收器,而是为了避免调用。布局更改期间的当前视图的 getView

这意味着传递给 getView 方法的 convertView 参数通常不为 null,这意味着您。会夸大很多观点(这是昂贵的) - 请参阅我对 的回答是否存在具有 View 回收功能的 Gallery 替代品? 以获得有关这方面的一些提示。 (PS:我已经修改了该代码 - 我会在布局阶段和滚动阶段使用不同的回收站,并根据其位置检索布局回收站中的视图,并且如果从垃圾箱中获得的视图是非空的,因为它在布局阶段之后也会清除布局回收站——这使得事情变得更加快捷)

PS:还要非常小心你所做的事情OnItemSelected - 即除非就在上面提到的地方,那就尽量少做。例如,我在 OnItemSelected 中的 Gallery 上方的 TextView 中设置一些文本。只需将此调用移至与我更新缩略图的位置相同的位置即可产生显着的差异。

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 call setCallbackDuringFling(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 use getFirstVisiblePosition and getLastVisiblePosition 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 thus OnItemSelected 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 whenever onItemSelected is called or when you get an ACTION_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.

仅冇旳回忆 2024-11-09 15:03:49

我有一个答案给你!

当内部在 ImageView 上调用任何 setImage... 方法时,会请求布局传递,例如上面的 setImageBitmap()被定义为这样的

public void setImageBitmap(Bitmap bm) {
    setImageDrawable(new BitmapDrawable(mContext.getResources(), bm));
}

,它调用

public void setImageDrawable(Drawable drawable) {
    if (mDrawable != drawable) {
        mResource = 0;
        mUri = null;
        updateDrawable(drawable);
        requestLayout(); //layout requested here!
        invalidate();
    }
}

具有画廊“捕捉”到当前最接近画廊中心的图像中心的效果。

为了防止这种情况,我所做的就是让加载到图库中的视图具有明确的高度和宽度(以 dip 为单位),并使用忽略布局请求的 ImageView 子类。这是有效的,因为图库最初仍然有一个布局通道,但每次图库中的图像发生变化时都不需要这样做,我想只有当图库视图的宽度和高度设置为 WRAP_CONTENT< 时才需要发生这种情况/code>,我们没有。请注意,由于 setImageDrawable() 中仍会调用 invalidate(),因此设置时仍会绘制图像。

下面是我非常简单的 ImageView 子类!

/**
 * 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
    }
}

编辑:我应该提到 onItemSelected 方法也可以工作,但是由于我需要在进行投掷时挂钩,所以我想出了上面的方法,我认为这是更灵活的方法

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 dips) 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

就是爱搞怪 2024-11-09 15:03:49

这可能是 Gallery 的 onLayout 方法中的错误。请查看 http://code.google.com/p/android/issues /detail?id=16171 了解可能的解决方法。

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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文