如何在使用Volley库和NetworkImageView的setImageUrl方法时获取完成回调?

30

我正在尝试使用Google的新Volley库,当我使用setImageUrl方法时,它看起来非常好,并且可以快速加载图像:

holder.image.setImageUrl(url, ImageCacheManager.getInstance().getImageLoader());

我希望为它添加一个回调/监听器方法,当加载完成时触发,以便我可以移除progressBar视图并显示图像。这是在Universal Image Loader和Picasso库中存在的选项,但是由于某些原因,我无法找到在Volley中实现此功能的方法,尝试了谷歌不同的选项,但到目前为止没有找到任何参考。

有人有可以说明如何实现的代码示例吗?

7个回答

19
您可以使用这个视图来替代谷歌的视图(我复制了它的源代码并做了一些修改):
public class VolleyImageView extends ImageView {

    public interface ResponseObserver
    {
        public void onError();
        public void onSuccess();
    }

    private ResponseObserver mObserver;

    public void setResponseObserver(ResponseObserver observer) {
        mObserver = observer;
    }

    /**
     * The URL of the network image to load
     */
    private String mUrl;

    /**
     * Resource ID of the image to be used as a placeholder until the network image is loaded.
     */
    private int mDefaultImageId;

    /**
     * Resource ID of the image to be used if the network response fails.
     */
    private int mErrorImageId;

    /**
     * Local copy of the ImageLoader.
     */
    private ImageLoader mImageLoader;

    /**
     * Current ImageContainer. (either in-flight or finished)
     */
    private ImageContainer mImageContainer;

    public VolleyImageView(Context context) {
        this(context, null);
    }

    public VolleyImageView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public VolleyImageView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }

    /**
     * Sets URL of the image that should be loaded into this view. Note that calling this will
     * immediately either set the cached image (if available) or the default image specified by
     * {@link VolleyImageView#setDefaultImageResId(int)} on the view.
     *
     * NOTE: If applicable, {@link VolleyImageView#setDefaultImageResId(int)} and {@link
     * VolleyImageView#setErrorImageResId(int)} should be called prior to calling this function.
     *
     * @param url         The URL that should be loaded into this ImageView.
     * @param imageLoader ImageLoader that will be used to make the request.
     */
    public void setImageUrl(String url, ImageLoader imageLoader) {
        mUrl = url;
        mImageLoader = imageLoader;
        // The URL has potentially changed. See if we need to load it.
        loadImageIfNecessary(false);
    }

    /**
     * Sets the default image resource ID to be used for this view until the attempt to load it
     * completes.
     */
    public void setDefaultImageResId(int defaultImage) {
        mDefaultImageId = defaultImage;
    }

    /**
     * Sets the error image resource ID to be used for this view in the event that the image
     * requested fails to load.
     */
    public void setErrorImageResId(int errorImage) {
        mErrorImageId = errorImage;
    }

    /**
     * Loads the image for the view if it isn't already loaded.
     *
     * @param isInLayoutPass True if this was invoked from a layout pass, false otherwise.
     */
    private void loadImageIfNecessary(final boolean isInLayoutPass) {
        int width = getWidth();
        int height = getHeight();

        boolean isFullyWrapContent = getLayoutParams() != null
                && getLayoutParams().height == LayoutParams.WRAP_CONTENT
                && getLayoutParams().width == LayoutParams.WRAP_CONTENT;
        // if the view's bounds aren't known yet, and this is not a wrap-content/wrap-content
        // view, hold off on loading the image.
        if (width == 0 && height == 0 && !isFullyWrapContent) {
            return;
        }

        // if the URL to be loaded in this view is empty, cancel any old requests and clear the
        // currently loaded image.
        if (TextUtils.isEmpty(mUrl)) {
            if (mImageContainer != null) {
                mImageContainer.cancelRequest();
                mImageContainer = null;
            }
            setDefaultImageOrNull();
            return;
        }

        // if there was an old request in this view, check if it needs to be canceled.
        if (mImageContainer != null && mImageContainer.getRequestUrl() != null) {
            if (mImageContainer.getRequestUrl().equals(mUrl)) {
                // if the request is from the same URL, return.
                return;
            } else {
                // if there is a pre-existing request, cancel it if it's fetching a different URL.
                mImageContainer.cancelRequest();
                setDefaultImageOrNull();
            }
        }

        // The pre-existing content of this view didn't match the current URL. Load the new image
        // from the network.
        ImageContainer newContainer = mImageLoader.get(mUrl,
                new ImageListener() {
                    @Override
                    public void onErrorResponse(VolleyError error) {
                        if (mErrorImageId != 0) {
                            setImageResource(mErrorImageId);
                        }

                        if(mObserver!=null)
                        {
                            mObserver.onError();
                        }
                    }

                    @Override
                    public void onResponse(final ImageContainer response, boolean isImmediate) {
                        // If this was an immediate response that was delivered inside of a layout
                        // pass do not set the image immediately as it will trigger a requestLayout
                        // inside of a layout. Instead, defer setting the image by posting back to
                        // the main thread.
                        if (isImmediate && isInLayoutPass) {
                            post(new Runnable() {
                                @Override
                                public void run() {
                                    onResponse(response, false);
                                }
                            });
                            return;
                        }

                        if (response.getBitmap() != null) {
                            setImageBitmap(response.getBitmap());
                        } else if (mDefaultImageId != 0) {
                            setImageResource(mDefaultImageId);
                        }

                        if(mObserver!=null)
                        {
                            mObserver.onSuccess();
                        }
                    }
                });

        // update the ImageContainer to be the new bitmap container.
        mImageContainer = newContainer;
    }

    private void setDefaultImageOrNull() {
        if (mDefaultImageId != 0) {
            setImageResource(mDefaultImageId);
        } else {
            setImageBitmap(null);
        }
    }

    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        super.onLayout(changed, left, top, right, bottom);
        loadImageIfNecessary(true);
    }

    @Override
    protected void onDetachedFromWindow() {
        if (mImageContainer != null) {
            // If the view was bound to an image request, cancel it and clear
            // out the image from the view.
            mImageContainer.cancelRequest();
            setImageBitmap(null);
            // also clear out the container so we can reload the image if necessary.
            mImageContainer = null;
        }
        super.onDetachedFromWindow();
    }

    @Override
    protected void drawableStateChanged() {
        super.drawableStateChanged();
        invalidate();
    }
}

使用示例:

 //set observer to view
 holder.image.setResponseObserver(new VolleyImageView.ResponseObserver() {
     @Override
     public void onError() {

     }

     @Override
     public void onSuccess() {

     }
 });

//and then load image
holder.image.setImageUrl(url, ImageCacheManager.getInstance().getImageLoader());

这段代码对我来说是有效的。但你应该添加导入,这样任何新手都能快速理解。谢谢。 - Gujarat Santana
@Kamlesh 欢迎,享受它 ;) - Yakiv Mospan
干得不错 - 但你应该添加导入,这样任何新手都能快速理解。这个地址对我很有帮助 https://github.com/mcxiaoke/android-volley/blob/master/src/main/java/com/android/volley/toolbox/NetworkImageView.java - sirmagid

7
我是这样做的:-
mImageLoader.get(url, new ImageLoader.ImageListener() {
    @Override
    public void onResponse(ImageLoader.ImageContainer response, boolean isImmediate) {
        if (response.getBitmap() != null)
            //some code
        else
            //some code
    }
    @Override
    public void onErrorResponse(VolleyError error) {
    }
});

6
我们使用类似下面的代码:
imageView.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
    @Override
    public void onLayoutChange(View view, int i, int i2, int i3, int i4, int i5, int i6, int i7, int i8) {
        // the layout of the logo view changes at least twice: When it is added
        // to the parent and when the image has been loaded. We are only interested
        // in the second case and to find that case, we do this if statement

        if (imageView.getDrawable() != null) {
          doSomethingCoolHere();
        }

    }
});

这段代码可能不是最漂亮的,但它能正常工作(商标)。


View.OnLayoutChangeListener 需要 API 级别 11。 - vfede

2
另一种方法是依赖于了解NetworkImageView的内部结构,通过创建NetworkImageView子类来监视mErrorImageId的应用。
public class ManagedNetworkImageView extends NetworkImageView{
    private int mErrorResId;

    public ManagedNetworkImageView(Context context) {
        super(context);
    }

    public ManagedNetworkImageView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public ManagedNetworkImageView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }

    @Override
    public void setErrorImageResId(int errorImage) {
        mErrorResId = errorImage;
        super.setErrorImageResId(errorImage);
    }

    @Override
    public void setImageResource(int resId) {
        if (resId == mErrorResId) {
            // TODO Handle the error here
        }
        super.setImageResource(resId);
    }

    @Override
    public void setImageBitmap(Bitmap bm) {
        // TODO Handle the success here
        super.setImageBitmap(bm);
    }
}

您还需要在布局文件中将NetworkImageView替换为ManagedNetworkImageView。

虽然有点巧妙,但当NetworkImageView已经是您选择的解决方案时,它可以胜任工作。


谢谢 - 因为我已经子类化了NetworkImageView,所以这对我来说非常完美! - Kibi
对我来说,这看起来是一个很好的解决方案,因为我已经拥有了自定义的NetworkImageView使其成为圆形。但我不明白的是,在ManagedNetworkImageView类中如何使用setImageBitmap方法处理成功事件,例如控制Activity/Fragment中的进度条或执行其他操作?需要通过回调来实现吗?您能提供一个基本示例吗? - iBobb

1
步骤1:声明imageLoader,(我有一个名为MySocialMediaSingleton的类来管理Volley请求)
ImageLoader imageLoader = MySocialMediaSingleton.getInstance(context).getImageLoader();

步骤2:使用图像加载器的回调函数。
imageLoader.get(url, new ImageLoader.ImageListener() {
        @Override
        public void onErrorResponse(VolleyError error) {
             //an error ocurred
        }

        @Override
        public void onResponse(ImageLoader.ImageContainer response, boolean isImmediate) {
            if (response.getBitmap() != null) {
                //loadingView gone
            } else {
                //some code
            }
        }
    });

步骤三:在您的imageView或NetworkImageView中显示响应。

holder.image.setImageUrl(ImageCacheManager.getInstance().getImageLoader(), imageLoader);

1
Listener<Bitmap> imageListener = new Listener<Bitmap>() {
    @Override
    public void onResponse(Bitmap response) {
        //This call back method is executed in the UI-Thread, when the loading is finished
        imageView.setImageBitmap(response); //example
    }
};
Response.ErrorListener errorListener = new Response.ErrorListener() {
    @Override
    public void onErrorResponse(VolleyError error) {
      //log your error
    }
};
//url, ListenerOnFinish, width, height, errorListener
ImageRequest getImageRequest = new ImageRequest(url, imageListener, 0, 0, null,errorListener);
requestQueue.add(getImageRequest);

是的,我在这里找到了一些类似的代码:http://stackoverflow.com/questions/17278866/android-volley-quickstart 但我不知道如何将其与 setImageUrl 方法相对应使用。你能解释一下吗? - Emil Adz
我没有使用 #setImageUrl,而是使用了 #setImageBitmap,所以我不知道这会怎么样。注意:请求也有缓存。因此,在短时间内两次加载图像将不会从网络下载图像两次。 - Simulant
1
当您使用setImageBitmap时,您是否正在从网络加载图像? - Emil Adz
@Emil Adz 不是的,这个图片正在“新的ImageRequest”上加载。 - Richard Fung
@EmilAdz 请给Simulant适当的荣誉。你的问题要求一个能力,而你的holder.image,即NetworkImageView并没有提供这个能力。你不能使用setImageUrl()并且也得到回调(截至2014年3月)。唯一获得回调的方法是手动发起请求,使用如下所示的ImageRequest。在他的注释中,Simulant提到了缓存:在使用手动请求图像时,请务必考虑手动缓存。Yakiv Mospan也解决了这个问题,但由于NetworkImageView没有暴露许多重写方法,他不得不复制/粘贴相当多的代码。 - larham1

0

另一种方法(类似于@Simulant上面的代码)是在xml中使用常规的ImageView,然后使用Volley.ImageRequest进行图像请求。如果您使用谷歌推荐的单例模式(Singleton pattern),它会看起来像这样:

ImageView mImageView = (ImageView) findViewById(R.id.myimageview);
RequestQueue requestQueue = MyVolleySingleton.getInstance(mContext).getRequestQueue();
ImageRequest mainImageRequest = new ImageRequest(myImageURL,
    new Response.Listener<Bitmap>() {
       @Override
       public void onResponse(Bitmap bitmap) {
          // set the image here
          mImageView.setImageBitmap(bitmap);
          // hide the spinner here
       }
    }, 0, 0, null, null);

 requestQueue.add(mainImageRequest);

顺便提一下:确保使用常规的ImageView而不是NetworkImageView,否则图像将无法正确显示。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接