Android Bitmap載入大圖片
在相機拍攝畫素越來越高的現在,高解析度的大圖已經很常見,手機載入高清大圖功能基本已成日常需要。但是,由於移動裝置本身記憶體和解析度的限制,通常會先載入縮圖然後根據需要展示大圖內容。
一、載入縮圖
1.讀取圖片大小和型別
BitmapFactory
提供了 decodeByteArray
、decodeStream
、decodeFile
、decodeResource
等方法建立一個Bitmap物件。通過設定BitmapFactory.Options
的 inJustDecodeBounds
為true,我們可以在不為Bitmap物件分配記憶體的情況下獲取Bitmap的寬、高、顯示質量(如ARGB_8888)、MIME值(如image/jpeg)、取樣比例等屬性。(如果inJustDecodeBounds
bitmap.getByteCount()
獲取Bitmap物件大小。)
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(getResources(), R.id.myimage, options);
int imageHeight = options.outHeight ;
int imageWidth = options.outWidth;
Bitmap.Config preferredConfig = options.inPreferredConfig;
String imageType = options.outMimeType;
一張在磁碟上只有幾 M 的圖片(JPG,PNG等壓縮格式),所需記憶體可能會幾十 M 。計算需要的記憶體大小
LogUtils.d("所需記憶體大小為"+(imageHeight*imageWidth*getBytesPerPixel(preferredConfig)/1024/1024)+"MB");
public int getBytesPerPixel(Bitmap.Config config) {
if (config == Bitmap.Config.ARGB_8888) {
return 4;
} else if (config == Bitmap.Config.RGB_565) {
return 2;
} else if (config == Bitmap.Config.ARGB_4444) {
return 2;
} else if (config == Bitmap.Config.ALPHA_8) {
return 1;
}
return 1;
}
2.獲取inSampleSize
圖片大小是由圖片質量和解析度決定的。例如一張1280x720的ARGB_8888
、RGB_565
圖片大小分別為 1280x720x4= 3.516MB 和 1280x720x2=1.758MB。 根據inSampleSize
文件,如果inSampleSize
的取值小於等於1均會被看做1,另外應為inSampleSize
的值會向下取2的n次冪的整數值。比如inSampleSize == 4
,那麼最終圖片的寬和高都將為原來的1/4,總畫素為原來的1/16。所以,inSampleSize
直接影響了圖片大小。
根據以上特點,可以根據自己需求計算 inSampleSize
。常用獲取 inSampleSize
程式碼如下:
public static int calculateInSampleSize(
BitmapFactory.Options options, int reqWidth, int reqHeight) {
// Raw height and width of image
final int height = options.outHeight;
final int width = options.outWidth;
int inSampleSize = 1;
if (height > reqHeight || width > reqWidth) {
final int halfHeight = height / 2;
final int halfWidth = width / 2;
// Calculate the largest inSampleSize value that is a power of 2 and keeps both
// height and width larger than the requested height and width.
while ((halfHeight / inSampleSize) >= reqHeight
&& (halfWidth / inSampleSize) >= reqWidth) {
inSampleSize *= 2;
}
}
return inSampleSize;
}
最終獲取Bitmap示例程式碼如下(注意inJustDecodeBounds
的取值):
public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId,
int reqWidth, int reqHeight) {
// First decode with inJustDecodeBounds=true to check dimensions
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(res, resId, options);
// Calculate inSampleSize
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
// Decode bitmap with inSampleSize set
options.inJustDecodeBounds = false;
return BitmapFactory.decodeResource(res, resId, options);
}
展示圖片資源:
mImageView.setImageBitmap(
decodeSampledBitmapFromResource(getResources(), R.id.myimage, 100, 100));
二、展示大圖內容
Android 中提供了 BitmapRegionDecoder
類支援圖片按區域載入
//構造BitmapRegionDecoder物件
BitmapRegionDecoder decoder = BitmapRegionDecoder.newInstance(myStream, false);
通常情況下我們會監聽手勢,改變Rect,顯示不同區域。如果有必要還可以傳入ScaleGestureDetector
監聽縮放手勢。
如果只有手勢監聽,在onTouch
的方法中隨著手勢改變mRect,並繪製圖片。
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
startX = (int) event.getX();
startY = (int) event.getY();
break;
case MotionEvent.ACTION_MOVE:
int endX = (int) event.getX();
int endY = (int) event.getY();
int moveX = endX - startX;//水平滑動距離
int moveY = endY - startY;//垂直滑動距離
//修改mRect
if (mImageWidth > getWidth()) {
mRect.right-=moveX;
mRect.left =mRect.right-getWidth();
}
if (mImageHeight > getHeight()) {
mRect.bottom-=moveY;
mRect.top =mRect.bottom-getHeight();
}
//重新繪製
notifyInvalidate();
startX = endX;
startY = endY;
break;
}
return true;
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//按mRect的區域繪製Bitmap
Bitmap bitmap = mRegionDecoder.decodeRegion(mRect, sOptions);
canvas.drawBitmap(bitmap, 0, 0, null);
}
- 手勢監聽滑動可以考慮用Scroller
- 繪製介面感覺卡頓,可以開啟執行緒提前建立Bitmap