ImageLoader类
package com.android.volley.toolbox;
import android.graphics.Bitmap;
import android.graphics.Bitmap.Config;
import android.os.Handler;
import android.os.Looper;
import android.support.annotation.MainThread;
import android.widget.ImageView;
import android.widget.ImageView.ScaleType;
import com.android.volley.Request;
import com.android.volley.RequestQueue;
import com.android.volley.Response.ErrorListener;
import com.android.volley.Response.Listener;
import com.android.volley.ResponseDelivery;
import com.android.volley.VolleyError;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
/**
* Helper that handles loading and caching images from remote URLs.
*
* <p>The simple way to use this class is to call {@link ImageLoader#get(String, ImageListener)} and
* to pass in the default image listener provided by {@link ImageLoader#getImageListener(ImageView,
* int, int)}. Note that all function calls to this class must be made from the main thread, and all
* responses will be delivered to the main thread as well. Custom {@link ResponseDelivery}s which
* don't use the main thread are not supported.
*/
public class ImageLoader {
/** RequestQueue for dispatching ImageRequests onto. */
private final RequestQueue mRequestQueue;
/** Amount of time to wait after first response arrives before delivering all responses. */
private int mBatchResponseDelayMs = 100;
/** The cache implementation to be used as an L1 cache before calling into volley. */
private final ImageCache mCache;
/**
* HashMap of Cache keys -> BatchedImageRequest used to track in-flight requests so that we can
* coalesce multiple requests to the same URL into a single network request.
*/
private final HashMap<String, BatchedImageRequest> mInFlightRequests = new HashMap<>();
/** HashMap of the currently pending responses (waiting to be delivered). */
private final HashMap<String, BatchedImageRequest> mBatchedResponses = new HashMap<>();
/** Handler to the main thread. */
private final Handler mHandler = new Handler(Looper.getMainLooper());
/** Runnable for in-flight response delivery. */
private Runnable mRunnable;
/**
* Simple cache adapter interface. If provided to the ImageLoader, it will be used as an L1
* cache before dispatch to Volley. Implementations must not block. Implementation with an
* LruCache is recommended.
*/
public interface ImageCache {
Bitmap getBitmap(String url);
void putBitmap(String url, Bitmap bitmap);
}
/**
* Constructs a new ImageLoader.
*
* @param queue The RequestQueue to use for making image requests.
* @param imageCache The cache to use as an L1 cache.
*/
public ImageLoader(RequestQueue queue, ImageCache imageCache) {
mRequestQueue = queue;
mCache = imageCache;
}
/**
* The default implementation of ImageListener which handles basic functionality of showing a
* default image until the network response is received, at which point it will switch to either
* the actual image or the error image.
*
* @param view The imageView that the listener is associated with.
* @param defaultImageResId Default image resource ID to use, or 0 if it doesn't exist.
* @param errorImageResId Error image resource ID to use, or 0 if it doesn't exist.
*/
public static ImageListener getImageListener(
final ImageView view, final int defaultImageResId, final int errorImageResId) {
return new ImageListener() {
@Override
public void onErrorResponse(VolleyError error) {
if (errorImageResId != 0) {
view.setImageResource(errorImageResId);
}
}
@Override
public void onResponse(ImageContainer response, boolean isImmediate) {
if (response.getBitmap() != null) {
view.setImageBitmap(response.getBitmap());
} else if (defaultImageResId != 0) {
view.setImageResource(defaultImageResId);
}
}
};
}
/**
* Interface for the response handlers on image requests.
*
* <p>The call flow is this: 1. Upon being attached to a request, onResponse(response, true)
* will be invoked to reflect any cached data that was already available. If the data was
* available, response.getBitmap() will be non-null.
*
* <p>2. After a network response returns, only one of the following cases will happen: -
* onResponse(response, false) will be called if the image was loaded. or - onErrorResponse will
* be called if there was an error loading the image.
*/
public interface ImageListener extends ErrorListener {
/**
* Listens for non-error changes to the loading of the image request.
*
* @param response Holds all information pertaining to the request, as well as the bitmap
* (if it is loaded).
* @param isImmediate True if this was called during ImageLoader.get() variants. This can be
* used to differentiate between a cached image loading and a network image loading in
* order to, for example, run an animation to fade in network loaded images.
*/
void onResponse(ImageContainer response, boolean isImmediate);
}
/**
* Checks if the item is available in the cache.
*
* @param requestUrl The url of the remote image
* @param maxWidth The maximum width of the returned image.
* @param maxHeight The maximum height of the returned image.
* @return True if the item exists in cache, false otherwise.
*/
public boolean isCached(String requestUrl, int maxWidth, int maxHeight) {
return isCached(requestUrl, maxWidth, maxHeight, ScaleType.CENTER_INSIDE);
}
/**
* Checks if the item is available in the cache.
*
* <p>Must be called from the main thread.
*
* @param requestUrl The url of the remote image
* @param maxWidth The maximum width of the returned image.
* @param maxHeight The maximum height of the returned image.
* @param scaleType The scaleType of the imageView.
* @return True if the item exists in cache, false otherwise.
*/
@MainThread
public boolean isCached(String requestUrl, int maxWidth, int maxHeight, ScaleType scaleType) {
Threads.throwIfNotOnMainThread();
String cacheKey = getCacheKey(requestUrl, maxWidth, maxHeight, scaleType);
return mCache.getBitmap(cacheKey) != null;
}
/**
* Returns an ImageContainer for the requested URL.
*
* <p>The ImageContainer will contain either the specified default bitmap or the loaded bitmap.
* If the default was returned, the {@link ImageLoader} will be invoked when the request is
* fulfilled.
*
* @param requestUrl The URL of the image to be loaded.
*/
public ImageContainer get(String requestUrl, final ImageListener listener) {
return get(requestUrl, listener, /* maxWidth= */ 0, /* maxHeight= */ 0);
}
/**
* Equivalent to calling {@link #get(String, ImageListener, int, int, ScaleType)} with {@code
* Scaletype == ScaleType.CENTER_INSIDE}.
*/
public ImageContainer get(
String requestUrl, ImageListener imageListener, int maxWidth, int maxHeight) {
return get(requestUrl, imageListener, maxWidth, maxHeight, ScaleType.CENTER_INSIDE);
}
/**
* Issues a bitmap request with the given URL if that image is not available in the cache, and
* returns a bitmap container that contains all of the data relating to the request (as well as
* the default image if the requested image is not available).
*
* <p>Must be called from the main thread.
*
* @param requestUrl The url of the remote image
* @param imageListener The listener to call when the remote image is loaded
* @param maxWidth The maximum width of the returned image.
* @param maxHeight The maximum height of the returned image.
* @param scaleType The ImageViews ScaleType used to calculate the needed image size.
* @return A container object that contains all of the properties of the request, as well as the
* currently available image (default if remote is not loaded).
*/
@MainThread
public ImageContainer get(
String requestUrl,
ImageListener imageListener,
int maxWidth,
int maxHeight,
ScaleType scaleType) {
// only fulfill requests that were initiated from the main thread.
Threads.throwIfNotOnMainThread();
final String cacheKey = getCacheKey(requestUrl, maxWidth, maxHeight, scaleType);
// Try to look up the request in the cache of remote images.
Bitmap cachedBitmap = mCache.getBitmap(cacheKey);
if (cachedBitmap != null) {
// Return the cached bitmap.
ImageContainer container =
new ImageContainer(
cachedBitmap, requestUrl, /* cacheKey= */ null, /* listener= */ null);
imageListener.onResponse(container, true);
return container;
}
// The bitmap did not exist in the cache, fetch it!
ImageContainer imageContainer =
new ImageContainer(null, requestUrl, cacheKey, imageListener);
// Update the caller to let them know that they should use the default bitmap.
imageListener.onResponse(imageContainer, true);
// Check to see if a request is already in-flight or completed but pending batch delivery.
BatchedImageRequest request = mInFlightRequests.get(cacheKey);
if (request == null) {
request = mBatchedResponses.get(cacheKey);
}
if (request != null) {
// If it is, add this request to the list of listeners.
request.addContainer(imageContainer);
return imageContainer;
}
// The request is not already in flight. Send the new request to the network and
// track it.
Request<Bitmap> newRequest =
makeImageRequest(requestUrl, maxWidth, maxHeight, scaleType, cacheKey);
mRequestQueue.add(newRequest);
mInFlightRequests.put(cacheKey, new BatchedImageRequest(newRequest, imageContainer));
return imageContainer;
}
protected Request<Bitmap> makeImageRequest(
String requestUrl,
int maxWidth,
int maxHeight,
ScaleType scaleType,
final String cacheKey) {
return new ImageRequest(
requestUrl,
new Listener<Bitmap>() {
@Override
public void onResponse(Bitmap response) {
onGetImageSuccess(cacheKey, response);
}
},
maxWidth,
maxHeight,
scaleType,
Config.RGB_565,
new ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
onGetImageError(cacheKey, error);
}
});
}
/**
* Sets the amount of time to wait after the first response arrives before delivering all
* responses. Batching can be disabled entirely by passing in 0.
*
* @param newBatchedResponseDelayMs The time in milliseconds to wait.
*/
public void setBatchedResponseDelay(int newBatchedResponseDelayMs) {
mBatchResponseDelayMs = newBatchedResponseDelayMs;
}
/**
* Handler for when an image was successfully loaded.
*
* @param cacheKey The cache key that is associated with the image request.
* @param response The bitmap that was returned from the network.
*/
protected void onGetImageSuccess(String cacheKey, Bitmap response) {
// cache the image that was fetched.
mCache.putBitmap(cacheKey, response);
// remove the request from the list of in-flight requests.
BatchedImageRequest request = mInFlightRequests.remove(cacheKey);
if (request != null) {
// Update the response bitmap.
request.mResponseBitmap = response;
// Send the batched response
batchResponse(cacheKey, request);
}
}
/**
* Handler for when an image failed to load.
*
* @param cacheKey The cache key that is associated with the image request.
*/
protected void onGetImageError(String cacheKey, VolleyError error) {
// Notify the requesters that something failed via a null result.
// Remove this request from the list of in-flight requests.
BatchedImageRequest request = mInFlightRequests.remove(cacheKey);
if (request != null) {
// Set the error for this request
request.setError(error);
// Send the batched response
batchResponse(cacheKey, request);
}
}
/** Container object for all of the data surrounding an image request. */
public class ImageContainer {
/**
* The most relevant bitmap for the container. If the image was in cache, the Holder to use
* for the final bitmap (the one that pairs to the requested URL).
*/
private Bitmap mBitmap;
private final ImageListener mListener;
/** The cache key that was associated with the request */
private final String mCacheKey;
/** The request URL that was specified */
private final String mRequestUrl;
/**
* Constructs a BitmapContainer object.
*
* @param bitmap The final bitmap (if it exists).
* @param requestUrl The requested URL for this container.
* @param cacheKey The cache key that identifies the requested URL for this container.
*/
public ImageContainer(
Bitmap bitmap, String requestUrl, String cacheKey, ImageListener listener) {
mBitmap = bitmap;
mRequestUrl = requestUrl;
mCacheKey = cacheKey;
mListener = listener;
}
/**
* Releases interest in the in-flight request (and cancels it if no one else is listening).
*
* <p>Must be called from the main thread.
*/
@MainThread
public void cancelRequest() {
Threads.throwIfNotOnMainThread();
if (mListener == null) {
return;
}
BatchedImageRequest request = mInFlightRequests.get(mCacheKey);
if (request != null) {
boolean canceled = request.removeContainerAndCancelIfNecessary(this);
if (canceled) {
mInFlightRequests.remove(mCacheKey);
}
} else {
// check to see if it is already batched for delivery.
request = mBatchedResponses.get(mCacheKey);
if (request != null) {
request.removeContainerAndCancelIfNecessary(this);
if (request.mContainers.size() == 0) {
mBatchedResponses.remove(mCacheKey);
}
}
}
}
/**
* Returns the bitmap associated with the request URL if it has been loaded, null otherwise.
*/
public Bitmap getBitmap() {
return mBitmap;
}
/** Returns the requested URL for this container. */
public String getRequestUrl() {
return mRequestUrl;
}
}
/**
* Wrapper class used to map a Request to the set of active ImageContainer objects that are
* interested in its results.
*/
private static class BatchedImageRequest {
/** The request being tracked */
private final Request<?> mRequest;
/** The result of the request being tracked by this item */
private Bitmap mResponseBitmap;
/** Error if one occurred for this response */
private VolleyError mError;
/** List of all of the active ImageContainers that are interested in the request */
private final List<ImageContainer> mContainers = new ArrayList<>();
/**
* Constructs a new BatchedImageRequest object
*
* @param request The request being tracked
* @param container The ImageContainer of the person who initiated the request.
*/
public BatchedImageRequest(Request<?> request, ImageContainer container) {
mRequest = request;
mContainers.add(container);
}
/** Set the error for this response */
public void setError(VolleyError error) {
mError = error;
}
/** Get the error for this response */
public VolleyError getError() {
return mError;
}
/**
* Adds another ImageContainer to the list of those interested in the results of the
* request.
*/
public void addContainer(ImageContainer container) {
mContainers.add(container);
}
/**
* Detaches the bitmap container from the request and cancels the request if no one is left
* listening.
*
* @param container The container to remove from the list
* @return True if the request was canceled, false otherwise.
*/
public boolean removeContainerAndCancelIfNecessary(ImageContainer container) {
mContainers.remove(container);
if (mContainers.size() == 0) {
mRequest.cancel();
return true;
}
return false;
}
}
/**
* Starts the runnable for batched delivery of responses if it is not already started.
*
* @param cacheKey The cacheKey of the response being delivered.
* @param request The BatchedImageRequest to be delivered.
*/
private void batchResponse(String cacheKey, BatchedImageRequest request) {
mBatchedResponses.put(cacheKey, request);
// If we don't already have a batch delivery runnable in flight, make a new one.
// Note that this will be used to deliver responses to all callers in mBatchedResponses.
if (mRunnable == null) {
mRunnable =
new Runnable() {
@Override
public void run() {
for (BatchedImageRequest bir : mBatchedResponses.values()) {
for (ImageContainer container : bir.mContainers) {
// If one of the callers in the batched request canceled the
// request
// after the response was received but before it was delivered,
// skip them.
if (container.mListener == null) {
continue;
}
if (bir.getError() == null) {
container.mBitmap = bir.mResponseBitmap;
container.mListener.onResponse(container, false);
} else {
container.mListener.onErrorResponse(bir.getError());
}
}
}
mBatchedResponses.clear();
mRunnable = null;
}
};
// Post the runnable.
mHandler.postDelayed(mRunnable, mBatchResponseDelayMs);
}
}
/**
* Creates a cache key for use with the L1 cache.
*
* @param url The URL of the request.
* @param maxWidth The max-width of the output.
* @param maxHeight The max-height of the output.
* @param scaleType The scaleType of the imageView.
*/
private static String getCacheKey(
String url, int maxWidth, int maxHeight, ScaleType scaleType) {
return new StringBuilder(url.length() + 12)
.append("#W")
.append(maxWidth)
.append("#H")
.append(maxHeight)
.append("#S")
.append(scaleType.ordinal())
.append(url)
.toString();
}
}
ImageLoader类的核心就是加载图片,对图片的处理,而图片的处理在下面这个类里面
package com.android.volley.toolbox;
import android.graphics.Bitmap;
import android.graphics.Bitmap.Config;
import android.graphics.BitmapFactory;
import android.support.annotation.GuardedBy;
import android.support.annotation.Nullable;
import android.support.annotation.VisibleForTesting;
import android.widget.ImageView.ScaleType;
import com.android.volley.DefaultRetryPolicy;
import com.android.volley.NetworkResponse;
import com.android.volley.ParseError;
import com.android.volley.Request;
import com.android.volley.Response;
import com.android.volley.VolleyLog;
/** A canned request for getting an image at a given URL and calling back with a decoded Bitmap. */
public class ImageRequest extends Request<Bitmap> {
/** Socket timeout in milliseconds for image requests */
public static final int DEFAULT_IMAGE_TIMEOUT_MS = 1000;
/** Default number of retries for image requests */
public static final int DEFAULT_IMAGE_MAX_RETRIES = 2;
/** Default backoff multiplier for image requests */
public static final float DEFAULT_IMAGE_BACKOFF_MULT = 2f;
/** Lock to guard mListener as it is cleared on cancel() and read on delivery. */
private final Object mLock = new Object();
@GuardedBy("mLock")
@Nullable
private Response.Listener<Bitmap> mListener;
private final Config mDecodeConfig;
private final int mMaxWidth;
private final int mMaxHeight;
private final ScaleType mScaleType;
/** Decoding lock so that we don't decode more than one image at a time (to avoid OOM's) */
private static final Object sDecodeLock = new Object();
/**
* Creates a new image request, decoding to a maximum specified width and height. If both width
* and height are zero, the image will be decoded to its natural size. If one of the two is
* nonzero, that dimension will be clamped and the other one will be set to preserve the image's
* aspect ratio. If both width and height are nonzero, the image will be decoded to be fit in
* the rectangle of dimensions width x height while keeping its aspect ratio.
*
* @param url URL of the image
* @param listener Listener to receive the decoded bitmap
* @param maxWidth Maximum width to decode this bitmap to, or zero for none
* @param maxHeight Maximum height to decode this bitmap to, or zero for none
* @param scaleType The ImageViews ScaleType used to calculate the needed image size.
* @param decodeConfig Format to decode the bitmap to
* @param errorListener Error listener, or null to ignore errors
*/
public ImageRequest(
String url,
Response.Listener<Bitmap> listener,
int maxWidth,
int maxHeight,
ScaleType scaleType,
Config decodeConfig,
@Nullable Response.ErrorListener errorListener) {
super(Method.GET, url, errorListener);
setRetryPolicy(
new DefaultRetryPolicy(
DEFAULT_IMAGE_TIMEOUT_MS,
DEFAULT_IMAGE_MAX_RETRIES,
DEFAULT_IMAGE_BACKOFF_MULT));
mListener = listener;
mDecodeConfig = decodeConfig;
mMaxWidth = maxWidth;
mMaxHeight = maxHeight;
mScaleType = scaleType;
}
/**
* For API compatibility with the pre-ScaleType variant of the constructor. Equivalent to the
* normal constructor with {@code ScaleType.CENTER_INSIDE}.
*/
@Deprecated
public ImageRequest(
String url,
Response.Listener<Bitmap> listener,
int maxWidth,
int maxHeight,
Config decodeConfig,
Response.ErrorListener errorListener) {
this(
url,
listener,
maxWidth,
maxHeight,
ScaleType.CENTER_INSIDE,
decodeConfig,
errorListener);
}
@Override
public Priority getPriority() {
return Priority.LOW;
}
/**
* Scales one side of a rectangle to fit aspect ratio.
*
* @param maxPrimary Maximum size of the primary dimension (i.e. width for max width), or zero
* to maintain aspect ratio with secondary dimension
* @param maxSecondary Maximum size of the secondary dimension, or zero to maintain aspect ratio
* with primary dimension
* @param actualPrimary Actual size of the primary dimension
* @param actualSecondary Actual size of the secondary dimension
* @param scaleType The ScaleType used to calculate the needed image size.
*/
private static int getResizedDimension(
int maxPrimary,
int maxSecondary,
int actualPrimary,
int actualSecondary,
ScaleType scaleType) {
// If no dominant value at all, just return the actual.
if ((maxPrimary == 0) && (maxSecondary == 0)) {
return actualPrimary;
}
// If ScaleType.FIT_XY fill the whole rectangle, ignore ratio.
if (scaleType == ScaleType.FIT_XY) {
if (maxPrimary == 0) {
return actualPrimary;
}
return maxPrimary;
}
// If primary is unspecified, scale primary to match secondary's scaling ratio.
if (maxPrimary == 0) {
double ratio = (double) maxSecondary / (double) actualSecondary;
return (int) (actualPrimary * ratio);
}
if (maxSecondary == 0) {
return maxPrimary;
}
double ratio = (double) actualSecondary / (double) actualPrimary;
int resized = maxPrimary;
// If ScaleType.CENTER_CROP fill the whole rectangle, preserve aspect ratio.
if (scaleType == ScaleType.CENTER_CROP) {
if ((resized * ratio) < maxSecondary) {
resized = (int) (maxSecondary / ratio);
}
return resized;
}
if ((resized * ratio) > maxSecondary) {
resized = (int) (maxSecondary / ratio);
}
return resized;
}
@Override
protected Response<Bitmap> parseNetworkResponse(NetworkResponse response) {
// Serialize all decode on a global lock to reduce concurrent heap usage.
synchronized (sDecodeLock) {
try {
return doParse(response);
} catch (OutOfMemoryError e) {
VolleyLog.e("Caught OOM for %d byte image, url=%s", response.data.length, getUrl());
return Response.error(new ParseError(e));
}
}
}
/** The real guts of parseNetworkResponse. Broken out for readability. */
private Response<Bitmap> doParse(NetworkResponse response) {
byte[] data = response.data;
BitmapFactory.Options decodeOptions = new BitmapFactory.Options();
Bitmap bitmap = null;
if (mMaxWidth == 0 && mMaxHeight == 0) {
decodeOptions.inPreferredConfig = mDecodeConfig;
bitmap = BitmapFactory.decodeByteArray(data, 0, data.length, decodeOptions);
} else {
// If we have to resize this image, first get the natural bounds.
decodeOptions.inJustDecodeBounds = true;
BitmapFactory.decodeByteArray(data, 0, data.length, decodeOptions);
int actualWidth = decodeOptions.outWidth;
int actualHeight = decodeOptions.outHeight;
// Then compute the dimensions we would ideally like to decode to.
int desiredWidth =
getResizedDimension(
mMaxWidth, mMaxHeight, actualWidth, actualHeight, mScaleType);
int desiredHeight =
getResizedDimension(
mMaxHeight, mMaxWidth, actualHeight, actualWidth, mScaleType);
// Decode to the nearest power of two scaling factor.
decodeOptions.inJustDecodeBounds = false;
// TODO(ficus): Do we need this or is it okay since API 8 doesn't support it?
// decodeOptions.inPreferQualityOverSpeed = PREFER_QUALITY_OVER_SPEED;
decodeOptions.inSampleSize =findBestSampleSize(actualWidth, actualHeight, desiredWidth, desiredHeight);
Bitmap tempBitmap = BitmapFactory.decodeByteArray(data, 0, data.length, decodeOptions);
// If necessary, scale down to the maximal acceptable size.
if (tempBitmap != null
&& (tempBitmap.getWidth() > desiredWidth
|| tempBitmap.getHeight() > desiredHeight)) {
bitmap = Bitmap.createScaledBitmap(tempBitmap, desiredWidth, desiredHeight, true);
tempBitmap.recycle();
} else {
bitmap = tempBitmap;
}
}
if (bitmap == null) {
return Response.error(new ParseError(response));
} else {
return Response.success(bitmap, HttpHeaderParser.parseCacheHeaders(response));
}
}
@Override
public void cancel() {
super.cancel();
synchronized (mLock) {
mListener = null;
}
}
@Override
protected void deliverResponse(Bitmap response) {
Response.Listener<Bitmap> listener;
synchronized (mLock) {
listener = mListener;
}
if (listener != null) {
listener.onResponse(response);
}
}
/**
* Returns the largest power-of-two divisor for use in downscaling a bitmap that will not result
* in the scaling past the desired dimensions.
*
* @param actualWidth Actual width of the bitmap
* @param actualHeight Actual height of the bitmap
* @param desiredWidth Desired width of the bitmap
* @param desiredHeight Desired height of the bitmap
*/
@VisibleForTesting
static int findBestSampleSize(
int actualWidth, int actualHeight, int desiredWidth, int desiredHeight) {
double wr = (double) actualWidth / desiredWidth;
double hr = (double) actualHeight / desiredHeight;
double ratio = Math.min(wr, hr);
float n = 1.0f;
while ((n * 2) <= ratio) {
n *= 2;
}
return (int) n;
}
}
下面这个方法是图片核心处理
/** The real guts of parseNetworkResponse. Broken out for readability. */
private Response<Bitmap> doParse(NetworkResponse response) {
byte[] data = response.data;
BitmapFactory.Options decodeOptions = new BitmapFactory.Options();
Bitmap bitmap = null;
if (mMaxWidth == 0 && mMaxHeight == 0) {
decodeOptions.inPreferredConfig = mDecodeConfig;
bitmap = BitmapFactory.decodeByteArray(data, 0, data.length, decodeOptions);
} else {
// If we have to resize this image, first get the natural bounds.
decodeOptions.inJustDecodeBounds = true;
BitmapFactory.decodeByteArray(data, 0, data.length, decodeOptions);
int actualWidth = decodeOptions.outWidth;
int actualHeight = decodeOptions.outHeight;
// Then compute the dimensions we would ideally like to decode to.
int desiredWidth =
getResizedDimension(
mMaxWidth, mMaxHeight, actualWidth, actualHeight, mScaleType);
int desiredHeight =
getResizedDimension(
mMaxHeight, mMaxWidth, actualHeight, actualWidth, mScaleType);
// Decode to the nearest power of two scaling factor.
decodeOptions.inJustDecodeBounds = false;
// TODO(ficus): Do we need this or is it okay since API 8 doesn't support it?
// decodeOptions.inPreferQualityOverSpeed = PREFER_QUALITY_OVER_SPEED;
decodeOptions.inSampleSize =findBestSampleSize(actualWidth, actualHeight, desiredWidth, desiredHeight);
Bitmap tempBitmap = BitmapFactory.decodeByteArray(data, 0, data.length, decodeOptions);
// If necessary, scale down to the maximal acceptable size.
if (tempBitmap != null
&& (tempBitmap.getWidth() > desiredWidth
|| tempBitmap.getHeight() > desiredHeight)) {
bitmap = Bitmap.createScaledBitmap(tempBitmap, desiredWidth, desiredHeight, true);
tempBitmap.recycle();
} else {
bitmap = tempBitmap;
}
}
if (bitmap == null) {
return Response.error(new ParseError(response));
} else {
return Response.success(bitmap, HttpHeaderParser.parseCacheHeaders(response));
}
}
1 首先根据传入进来的设置的图片高度和宽度,如果宽度和高度是0的话,就默认直接返回解析后的图片。
if (mMaxWidth == 0 && mMaxHeight == 0) {
decodeOptions.inPreferredConfig = mDecodeConfig;
bitmap = BitmapFactory.decodeByteArray(data, 0, data.length, decodeOptions);
}
2 然后计算实际的宽度和高度
decodeOptions.inJustDecodeBounds = true;
BitmapFactory.decodeByteArray(data, 0, data.length, decodeOptions);
int actualWidth = decodeOptions.outWidth;
int actualHeight = decodeOptions.outHeight;
3 保持图片的纵横比,算出需要的高度和宽度
// Then compute the dimensions we would ideally like to decode to.
int desiredWidth =
getResizedDimension(
mMaxWidth, mMaxHeight, actualWidth, actualHeight, mScaleType);
int desiredHeight =
getResizedDimension(
mMaxHeight, mMaxWidth, actualHeight, actualWidth, mScaleType);
4 根据上面的参数加载出图片
// Decode to the nearest power of two scaling factor.
decodeOptions.inJustDecodeBounds = false;
// TODO(ficus): Do we need this or is it okay since API 8 doesn't support it?
// decodeOptions.inPreferQualityOverSpeed = PREFER_QUALITY_OVER_SPEED;
decodeOptions.inSampleSize =findBestSampleSize(actualWidth, actualHeight, desiredWidth, desiredHeight);
Bitmap tempBitmap = BitmapFactory.decodeByteArray(data, 0, data.length, decodeOptions);
// If necessary, scale down to the maximal acceptable size.
if (tempBitmap != null
&& (tempBitmap.getWidth() > desiredWidth
|| tempBitmap.getHeight() > desiredHeight)) {
bitmap = Bitmap.createScaledBitmap(tempBitmap, desiredWidth, desiredHeight, true);
tempBitmap.recycle();
} else {
bitmap = tempBitmap;
}
注意3中的计算方法
/**
* Scales one side of a rectangle to fit aspect ratio.
*
* @param maxPrimary Maximum size of the primary dimension (i.e. width for max width), or zero
* to maintain aspect ratio with secondary dimension
* @param maxSecondary Maximum size of the secondary dimension, or zero to maintain aspect ratio
* with primary dimension
* @param actualPrimary Actual size of the primary dimension
* @param actualSecondary Actual size of the secondary dimension
* @param scaleType The ScaleType used to calculate the needed image size.
*/
private static int getResizedDimension(
int maxPrimary,
int maxSecondary,
int actualPrimary,
int actualSecondary,
ScaleType scaleType) {
// If no dominant value at all, just return the actual.
if ((maxPrimary == 0) && (maxSecondary == 0)) {
return actualPrimary;
}
// If ScaleType.FIT_XY fill the whole rectangle, ignore ratio.
if (scaleType == ScaleType.FIT_XY) {
if (maxPrimary == 0) {
return actualPrimary;
}
return maxPrimary;
}
// If primary is unspecified, scale primary to match secondary's scaling ratio.
if (maxPrimary == 0) {
double ratio = (double) maxSecondary / (double) actualSecondary;
return (int) (actualPrimary * ratio);
}
if (maxSecondary == 0) {
return maxPrimary;
}
double ratio = (double) actualSecondary / (double) actualPrimary;
int resized = maxPrimary;
// If ScaleType.CENTER_CROP fill the whole rectangle, preserve aspect ratio.
if (scaleType == ScaleType.CENTER_CROP) {
if ((resized * ratio) < maxSecondary) {
resized = (int) (maxSecondary / ratio);
}
return resized;
}
if ((resized * ratio) > maxSecondary) {
resized = (int) (maxSecondary / ratio);
}
return resized;
}
可以看得出来,设计的是很巧妙和周全的,而且还考虑到了ImageView.scale
Volley基本上是提供了一个加载图片的样例给大家,平时直接拿来用就好,特别图片需要自己处理的时候。