2013年11月19日火曜日

Android Volley の NetworkImageView で Bitmap の最大サイズを指定する

Volley の NetworkImageView は便利なのですが、Bitmap のサイズを最適化してくれません。

NetworkImageView で画像のダウンロードを開始するのが loadImageIfNecessary() です。

https://android.googlesource.com/platform/frameworks/volley/+/master/src/com/android/volley/toolbox/NetworkImageView.java
  1. public class NetworkImageView extends ImageView {  
  2.   
  3.     ...  
  4.   
  5.     /** Local copy of the ImageLoader. */  
  6.     private ImageLoader mImageLoader;  
  7.   
  8.     ...  
  9.   
  10.     public void setImageUrl(String url, ImageLoader imageLoader) {  
  11.         mUrl = url;  
  12.         mImageLoader = imageLoader;  
  13.         // The URL has potentially changed. See if we need to load it.  
  14.         loadImageIfNecessary(false);  
  15.     }  
  16.   
  17.     ...  
  18.   
  19.     /** 
  20.      * Loads the image for the view if it isn't already loaded. 
  21.      * @param isInLayoutPass True if this was invoked from a layout pass, false otherwise. 
  22.      */  
  23.     private void loadImageIfNecessary(final boolean isInLayoutPass) {  
  24.          
  25.         ...  
  26.   
  27.         // The pre-existing content of this view didn't match the current URL. Load the new image  
  28.         // from the network.  
  29.         ImageContainer newContainer = mImageLoader.get(mUrl,  
  30.                 new ImageListener() {  
  31.                     @Override  
  32.                     public void onErrorResponse(VolleyError error) {  
  33.                         if (mErrorImageId != 0) {  
  34.                             setImageResource(mErrorImageId);  
  35.                         }  
  36.                     }  
  37.   
  38.                     @Override  
  39.                     public void onResponse(final ImageContainer response, boolean isImmediate) {  
  40.                         // If this was an immediate response that was delivered inside of a layout  
  41.                         // pass do not set the image immediately as it will trigger a requestLayout  
  42.                         // inside of a layout. Instead, defer setting the image by posting back to  
  43.                         // the main thread.  
  44.                         if (isImmediate && isInLayoutPass) {  
  45.                             post(new Runnable() {  
  46.                                 @Override  
  47.                                 public void run() {  
  48.                                     onResponse(response, false);  
  49.                                 }  
  50.                             });  
  51.                             return;  
  52.                         }  
  53.   
  54.                         if (response.getBitmap() != null) {  
  55.                             setImageBitmap(response.getBitmap());  
  56.                         } else if (mDefaultImageId != 0) {  
  57.                             setImageResource(mDefaultImageId);  
  58.                         }  
  59.                     }  
  60.                 });  
  61.   
  62.         // update the ImageContainer to be the new bitmap container.  
  63.         mImageContainer = newContainer;  
  64.     }  
  65.   
  66.     ...  
  67. }  
ここで ImageLoader の get(url, imageListener) を呼んでいます。
ImageLoader には引数が4つの get(url, imageLoader, maxWidth, maxHeight) もあり、引数が2つの get() を呼んだ場合は、maxWidth, maxHeight には 0 が渡され、生成される Bitmap は実際の画像サイズになります。
  1. public class ImageLoader {  
  2.   
  3.     ...  
  4.   
  5.     public ImageContainer get(String requestUrl, final ImageListener listener) {  
  6.         return get(requestUrl, listener, 00);  
  7.     }  
  8.   
  9.     public ImageContainer get(String requestUrl, ImageListener imageListener,  
  10.             int maxWidth, int maxHeight) {  
  11.         // only fulfill requests that were initiated from the main thread.  
  12.         throwIfNotOnMainThread();  
  13.   
  14.         final String cacheKey = getCacheKey(requestUrl, maxWidth, maxHeight);  
  15.   
  16.         // Try to look up the request in the cache of remote images.  
  17.         Bitmap cachedBitmap = mCache.getBitmap(cacheKey);  
  18.         if (cachedBitmap != null) {  
  19.             // Return the cached bitmap.  
  20.             ImageContainer container = new ImageContainer(cachedBitmap, requestUrl, nullnull);  
  21.             imageListener.onResponse(container, true);  
  22.             return container;  
  23.         }  
  24.   
  25.         // The bitmap did not exist in the cache, fetch it!  
  26.         ImageContainer imageContainer =  
  27.                 new ImageContainer(null, requestUrl, cacheKey, imageListener);  
  28.   
  29.         // Update the caller to let them know that they should use the default bitmap.  
  30.         imageListener.onResponse(imageContainer, true);  
  31.   
  32.         // Check to see if a request is already in-flight.  
  33.         BatchedImageRequest request = mInFlightRequests.get(cacheKey);  
  34.         if (request != null) {  
  35.             // If it is, add this request to the list of listeners.  
  36.             request.addContainer(imageContainer);  
  37.             return imageContainer;  
  38.         }  
  39.   
  40.         // The request is not already in flight. Send the new request to the network and  
  41.         // track it.  
  42.         Request<?> newRequest =  
  43.             new ImageRequest(requestUrl, new Listener<Bitmap>() {  
  44.                 @Override  
  45.                 public void onResponse(Bitmap response) {  
  46.                     onGetImageSuccess(cacheKey, response);  
  47.                 }  
  48.             }, maxWidth, maxHeight,  
  49.             Config.RGB_565, new ErrorListener() {  
  50.                 @Override  
  51.                 public void onErrorResponse(VolleyError error) {  
  52.                     onGetImageError(cacheKey, error);  
  53.                 }  
  54.             });  
  55.   
  56.         mRequestQueue.add(newRequest);  
  57.         mInFlightRequests.put(cacheKey,  
  58.                 new BatchedImageRequest(newRequest, imageContainer));  
  59.         return imageContainer;  
  60.     }  
  61. }  
maxWidth と maxHeight は ImageRequest のコンストラクタに渡されています。
  1. public class ImageRequest extends Request<Bitmap> {  
  2.   
  3.     ...  
  4.   
  5.     private final int mMaxWidth;  
  6.     private final int mMaxHeight;  
  7.   
  8.     ...  
  9.   
  10.     public ImageRequest(String url, Response.Listener<Bitmap> listener, int maxWidth, int maxHeight,  
  11.             Config decodeConfig, Response.ErrorListener errorListener) {  
  12.         super(Method.GET, url, errorListener);  
  13.         setRetryPolicy(  
  14.                 new DefaultRetryPolicy(IMAGE_TIMEOUT_MS, IMAGE_MAX_RETRIES, IMAGE_BACKOFF_MULT));  
  15.         mListener = listener;  
  16.         mDecodeConfig = decodeConfig;  
  17.         mMaxWidth = maxWidth;  
  18.         mMaxHeight = maxHeight;  
  19.     }  
  20.   
  21.     ...  
  22.   
  23.     /** 
  24.      * The real guts of parseNetworkResponse. Broken out for readability. 
  25.      */  
  26.     private Response<Bitmap> doParse(NetworkResponse response) {  
  27.         byte[] data = response.data;  
  28.         BitmapFactory.Options decodeOptions = new BitmapFactory.Options();  
  29.         Bitmap bitmap = null;  
  30.         if (mMaxWidth == 0 && mMaxHeight == 0) {  
  31.             decodeOptions.inPreferredConfig = mDecodeConfig;  
  32.             bitmap = BitmapFactory.decodeByteArray(data, 0, data.length, decodeOptions);  
  33.         } else {  
  34.             // If we have to resize this image, first get the natural bounds.  
  35.             decodeOptions.inJustDecodeBounds = true;  
  36.             BitmapFactory.decodeByteArray(data, 0, data.length, decodeOptions);  
  37.             int actualWidth = decodeOptions.outWidth;  
  38.             int actualHeight = decodeOptions.outHeight;  
  39.   
  40.             // Then compute the dimensions we would ideally like to decode to.  
  41.             int desiredWidth = getResizedDimension(mMaxWidth, mMaxHeight,  
  42.                     actualWidth, actualHeight);  
  43.             int desiredHeight = getResizedDimension(mMaxHeight, mMaxWidth,  
  44.                     actualHeight, actualWidth);  
  45.   
  46.             // Decode to the nearest power of two scaling factor.  
  47.             decodeOptions.inJustDecodeBounds = false;  
  48.             // TODO(ficus): Do we need this or is it okay since API 8 doesn't support it?  
  49.             // decodeOptions.inPreferQualityOverSpeed = PREFER_QUALITY_OVER_SPEED;  
  50.             decodeOptions.inSampleSize =  
  51.                 findBestSampleSize(actualWidth, actualHeight, desiredWidth, desiredHeight);  
  52.             Bitmap tempBitmap =  
  53.                 BitmapFactory.decodeByteArray(data, 0, data.length, decodeOptions);  
  54.   
  55.             // If necessary, scale down to the maximal acceptable size.  
  56.             if (tempBitmap != null && (tempBitmap.getWidth() > desiredWidth ||  
  57.                     tempBitmap.getHeight() > desiredHeight)) {  
  58.                 bitmap = Bitmap.createScaledBitmap(tempBitmap,  
  59.                         desiredWidth, desiredHeight, true);  
  60.                 tempBitmap.recycle();  
  61.             } else {  
  62.                 bitmap = tempBitmap;  
  63.             }  
  64.         }  
  65.   
  66.         if (bitmap == null) {  
  67.             return Response.error(new ParseError(response));  
  68.         } else {  
  69.             return Response.success(bitmap, HttpHeaderParser.parseCacheHeaders(response));  
  70.         }  
  71.     }  
  72. }  
ImageRequest の doParse() で、mMaxWidth == 0 && mMaxHeight == 0 のときはバイト配列をそのまま Bitmap にしているのがわかりますね。
それ以外のときは BitmapFactory.Options の inJustDecodeBounds や inSampleSize を使って Bitmap をスケールしています。

以下では、View のサイズがわかっている場合はそのサイズを使い、わからないときは画面サイズを指定するようにしてみました。
  1. public class NetworkImageView extends ImageView {  
  2.   
  3.     ...  
  4.   
  5.     private void loadImageIfNecessary(final boolean isInLayoutPass) {  
  6.         int width = getWidth();  
  7.         int height = getHeight();  
  8.   
  9.         ...  
  10.   
  11.         DisplayMetrics metrics = getResources().getDisplayMetrics();  
  12.         int w = width > 0 ? width : metrics.widthPixels;  
  13.         int h = height > 0 ? height : metrics.heightPixels;  
  14.   
  15.         // The pre-existing content of this view didn't match the current URL. Load the new image  
  16.         // from the network.  
  17.         ImageContainer newContainer = mImageLoader.get(mUrl,  
  18.                 new ImageListener() {  
  19.                     @Override  
  20.                     public void onErrorResponse(VolleyError error) {  
  21.                         if (mErrorImageId != 0) {  
  22.                             setImageResource(mErrorImageId);  
  23.                         }  
  24.                     }  
  25.   
  26.                     @Override  
  27.                     public void onResponse(final ImageContainer response, boolean isImmediate) {  
  28.                         // If this was an immediate response that was delivered inside of a layout  
  29.                         // pass do not set the image immediately as it will trigger a requestLayout  
  30.                         // inside of a layout. Instead, defer setting the image by posting back to  
  31.                         // the main thread.  
  32.                         if (isImmediate && isInLayoutPass) {  
  33.                             post(new Runnable() {  
  34.                                 @Override  
  35.                                 public void run() {  
  36.                                     onResponse(response, false);  
  37.                                 }  
  38.                             });  
  39.                             return;  
  40.                         }  
  41.   
  42.                         if (response.getBitmap() != null) {  
  43.                             setImageBitmap(response.getBitmap());  
  44.                         } else if (mDefaultImageId != 0) {  
  45.                             setImageResource(mDefaultImageId);  
  46.                         }  
  47.                     }  
  48.                 }, w, h);  
  49.   
  50.         // update the ImageContainer to be the new bitmap container.  
  51.         mImageContainer = newContainer;  
  52.     }  
  53. }  




0 件のコメント:

コメントを投稿