低內存的手機如果直接加載大圖片,往往會出現OOM的情況.即便是主流手機,也不能無限制的加載大圖片.所以在顯示圖片之前,需要對圖片處理,把圖片縮放為最合適的尺寸再顯示.
網上很大方法都是不管三七二十一,直接壓縮圖片.這樣可能會導致圖片失真,顯示模糊.我采用的方式是,顯示尺寸有多大,就等比例壓縮成多大尺寸的圖片,關鍵關於在於如何尋找最合適的尺寸,下面分享兩個關鍵方法,提取至google開源框架volley
private static int getResizedDimension(int maxPrimary, int maxSecondary, int actualPrimary, int actualSecondary) { // If no dominant value at all, just return the actual. if (maxPrimary == 0 && maxSecondary == 0) { return actualPrimary; } // 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 (resized * ratio > maxSecondary) { resized = (int) (maxSecondary / ratio); } return resized; } private 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; }
圖片源可能來至不同的地方,如Assets,byte[],Bitmap,Uri,Resource,File.針對不同的源采用不同的方法,但是大同小異

public static Bitmap getImageFromAssetsFile(String fileName, int mMaxWidth, int mMaxHeight) { BitmapFactory.Options decodeOptions = new BitmapFactory.Options(); Bitmap bitmap = null; Config preferredConfig = Config.RGB_565; AssetManager am = Global.mAppCxt.getResources().getAssets(); try { if (mMaxWidth == 0 && mMaxHeight == 0) { decodeOptions.inPreferredConfig = preferredConfig; InputStream is = am.open(fileName); bitmap = BitmapFactory.decodeStream(is, null, decodeOptions); is.close(); } else { // If we have to resize this image, first get the natural // bounds. decodeOptions.inJustDecodeBounds = true; decodeOptions.inPreferredConfig = preferredConfig; InputStream is = am.open(fileName); BitmapFactory.decodeStream(is, null, decodeOptions); is.close(); 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); int desiredHeight = getResizedDimension(mMaxHeight, mMaxWidth, actualHeight, actualWidth); // 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); decodeOptions.inPreferredConfig = preferredConfig; InputStream is2 = am.open(fileName); Bitmap tempBitmap = BitmapFactory.decodeStream(is2, null, decodeOptions); is2.close(); // 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; } } } catch (IOException e) { e.printStackTrace(); } return bitmap; } public static Bitmap getImageFromData(byte[] data, int mMaxWidth, int mMaxHeight) { BitmapFactory.Options decodeOptions = new BitmapFactory.Options(); Bitmap bitmap = null; if (mMaxWidth == 0 && mMaxHeight == 0) { decodeOptions.inPreferredConfig = Config.RGB_565; 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); int desiredHeight = getResizedDimension(mMaxHeight, mMaxWidth, actualHeight, actualWidth); // 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; } } return bitmap; } public static Bitmap getImageFromBitmap(Bitmap srcBitmap, int mMaxWidth, int mMaxHeight) { Bitmap bitmap = null; if (mMaxWidth == 0 && mMaxHeight == 0) { bitmap = srcBitmap; } else { int actualWidth = srcBitmap.getWidth(); int actualHeight = srcBitmap.getHeight(); // Then compute the dimensions we would ideally like to decode to. int desiredWidth = getResizedDimension(mMaxWidth, mMaxHeight, actualWidth, actualHeight); int desiredHeight = getResizedDimension(mMaxHeight, mMaxWidth, actualHeight, actualWidth); bitmap = Bitmap.createScaledBitmap(srcBitmap, desiredWidth, desiredHeight, true); } return bitmap; } public static Bitmap getImageFromUri(Uri uri, int mMaxWidth, int mMaxHeight) { BitmapFactory.Options decodeOptions = new BitmapFactory.Options(); Bitmap bitmap = null; Config preferredConfig = Config.RGB_565; try { if (mMaxWidth == 0 && mMaxHeight == 0) { decodeOptions.inPreferredConfig = preferredConfig; InputStream is = Global.mAppCxt.getContentResolver() .openInputStream(uri); bitmap = BitmapFactory.decodeStream(is, null, decodeOptions); is.close(); } else { // If we have to resize this image, first get the natural // bounds. decodeOptions.inJustDecodeBounds = true; decodeOptions.inPreferredConfig = preferredConfig; InputStream is = Global.mAppCxt.getContentResolver() .openInputStream(uri); BitmapFactory.decodeStream(is, null, decodeOptions); is.close(); 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); int desiredHeight = getResizedDimension(mMaxHeight, mMaxWidth, actualHeight, actualWidth); // 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); decodeOptions.inPreferredConfig = preferredConfig; InputStream is2 = Global.mAppCxt.getContentResolver() .openInputStream(uri); Bitmap tempBitmap = BitmapFactory.decodeStream(is2, null, decodeOptions); is2.close(); // 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; } } } catch (IOException e) { e.printStackTrace(); } return bitmap; } public static Bitmap getImageFromResource(int resId, int mMaxWidth, int mMaxHeight) { BitmapFactory.Options decodeOptions = new BitmapFactory.Options(); Bitmap bitmap = null; Config preferredConfig = Config.RGB_565; try { if (mMaxWidth == 0 && mMaxHeight == 0) { decodeOptions.inPreferredConfig = preferredConfig; InputStream is = Global.mAppCxt.getResources().openRawResource( resId); bitmap = BitmapFactory.decodeStream(is, null, decodeOptions); is.close(); } else { // If we have to resize this image, first get the natural // bounds. decodeOptions.inJustDecodeBounds = true; decodeOptions.inPreferredConfig = preferredConfig; InputStream is = Global.mAppCxt.getResources().openRawResource( resId); BitmapFactory.decodeStream(is, null, decodeOptions); is.close(); 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); int desiredHeight = getResizedDimension(mMaxHeight, mMaxWidth, actualHeight, actualWidth); // 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); decodeOptions.inPreferredConfig = preferredConfig; InputStream is2 = Global.mAppCxt.getResources() .openRawResource(resId); Bitmap tempBitmap = BitmapFactory.decodeStream(is2, null, decodeOptions); is2.close(); // 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; } } } catch (IOException e) { e.printStackTrace(); } return bitmap; } public static Bitmap getImageFromFile(File file, int mMaxWidth, int mMaxHeight) { BitmapFactory.Options decodeOptions = new BitmapFactory.Options(); Bitmap bitmap = null; Config preferredConfig = Config.RGB_565; try { if (mMaxWidth == 0 && mMaxHeight == 0) { bitmap = BitmapFactory.decodeFile(file.getPath()); } else { // If we have to resize this image, first get the natural // bounds. decodeOptions.inJustDecodeBounds = true; decodeOptions.inPreferredConfig = preferredConfig; bitmap = BitmapFactory .decodeFile(file.getPath(), 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); int desiredHeight = getResizedDimension(mMaxHeight, mMaxWidth, actualHeight, actualWidth); // 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); decodeOptions.inPreferredConfig = preferredConfig; Bitmap tempBitmap = BitmapFactory.decodeFile(file.getPath(), 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; } } } catch (Exception e) { e.printStackTrace(); } return bitmap; }
關鍵代碼decodeOptions.inJustDecodeBounds = true;指得是只繪制邊界,不直接加載圖片,通過邊界尺寸再結合之前的兩個方法計算最合適的尺寸后才加載圖片
Config preferredConfig = Config.RGB_565;不支持透明度,如果需要透明度,就替換成ARGB_8888,但是內存占用會增加一倍.具體可以參考源碼的解釋
RGB_565,Each pixel is stored on 2 bytes
ARGB_8888,Each pixel is stored on 4 bytes