【问题标题】:How to get finish callback on setImageUrl with Volley library and NetworkImageView?如何使用 Volley 库和 NetworkImageView 在 setImageUrl 上完成回调?
【发布时间】:2013-12-16 01:16:47
【问题描述】:

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

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

我想添加一个回调/侦听器方法,该方法将在加载完成时启动,因此我可以删除 progressBar 视图并显示图像。这是 Universal Image Loader 和 Picasso 库中存在的一个选项,但是 出于某种原因,我在 Volley 中找不到这样做的方法,尝试用谷歌搜索不同的选项,但到目前为止还没有找到任何参考。

有人有代码示例来说明它是如何完成的吗?

【问题讨论】:

    标签: android image callback listener android-volley


    【解决方案1】:

    您可以使用此视图代替 Google 的视图(我已从中复制源代码并进行了一些更改):

    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());
    

    【讨论】:

    • 这段代码对我有用.. 但你也应该添加导入,所以如果有新手可以快速理解,谢谢
    • @Kamlesh 欢迎,尽情享受吧 ;)
    • 干得好-但您也应该添加导入,所以如果有人对此不熟悉,可以很快理解。这个地址帮我github.com/mcxiaoke/android-volley/blob/master/src/main/java/…
    【解决方案2】:

    我是这样做的:-

    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) {
        }
    });
    

    【讨论】:

      【解决方案3】:

      我们使用了这样的东西:

      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();
              }
      
          }
      });
      

      它不一定是最漂亮的一段代码,但它确实有效(tm)

      【讨论】:

      • View.OnLayoutChangeListener 需要 API 级别 11
      【解决方案4】:

      另一种依赖于了解 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。

      这是一个 bit hacky,但当 NetworkImageView 已经是您选择的解决方案时,它就可以完成这项工作。

      【讨论】:

      • 谢谢 - 因为我已经继承了 NetworkImageView,这对我来说是完美的!
      • 这对我来说似乎是一个很好的解决方案,而且我已经有一个自定义的 NetworkImageView 使其成为圆形。我无法理解的是,我们如何使用 ManagedNetworkImageView 类中的 setImageBitmap 方法来处理成功,例如控制 Activity/Fragment 中的进度条或做其他事情?是通过回电吗?你能提供一个基本的例子吗?
      【解决方案5】:
      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);
      

      【讨论】:

      • 是的,我在这里找到了一些类似的代码:stackoverflow.com/questions/17278866/android-volley-quickstart 但我不知道如何使用它与 setImageUrl 方法相对应。你能解释一下吗?
      • 我没有使用#setImageUrl,而是使用了#setImageBitmap,所以我不知道这将如何工作。注意:请求也有缓存。因此,在短时间内加载两次图像不会从网络上下载图像。
      • 您在使用 setImageBitmap 时是否从 Web 加载图像?
      • @Emil Adz 不,图像正在“new ImageRequest”上加载
      • @EmilAdz 请感谢 Simulant。您的问题要求您的 holder.image(NetworkImageView)不提供的功能。您不能使用 setImageUrl() 并获得回调(截至 2014 年 3 月)。获得回调的唯一方法是手动发出请求,如图所示使用 ImageRequest。在他的说明中,Simulant 提到了缓存:在对图像使用手动请求时,一定要结合手动缓存。 Yakiv Mospan 也解决了这个问题,但是因为 NetworkImageView 没有暴露很多覆盖的方法,所以他不得不复制/粘贴很多。
      【解决方案6】:

      第 1 步:声明 imageLoader,(我有一个 MySocialMediaSingleton 类来管理 Volley 请求)

      ImageLoader imageLoader = MySocialMediaSingleton.getInstance(context).getImageLoader();
      

      第 2 步:使用 imageLoader 的回调

      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
                  }
              }
          });
      

      第 3 步:在 imageView 或 NetworkImageView 中显示响应

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

      【讨论】:

        【解决方案7】:

        另一种方法(类似于上面 @Simulant 的代码)是在您的 xml 中使用常规 ImageView,然后使用 Volley.ImageRequest 发出图像请求。如果你使用 recommended by Google 的 Singleton 模式,它看起来像这样:

        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,否则图像将无法正确显示。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2013-09-05
          • 1970-01-01
          • 1970-01-01
          • 2020-09-15
          • 1970-01-01
          • 1970-01-01
          • 2015-11-16
          相关资源
          最近更新 更多