在相机拍摄像素越来越高的现在,高分辨率的大图已经很常见,手机加载高清大图功能基本已成日常需要。但是,由于移动设备本身内存和分辨率的限制,通常会先加载缩略图然后根据需要展示大图内容。
BitmapFactory
提供了 decodeByteArray
、decodeStream
、decodeFile
、decodeResource
等方法创建一个Bitmap对象。通过设置BitmapFactory.Options
的 inJustDecodeBounds
为true,我们可以在不为Bitmap对象分配内存的情况下获取Bitmap的宽、高、显示质量(如ARGB_8888)、MIME值(如image/jpeg)、采样比例等属性。(如果inJustDecodeBounds
为false,就会加载Bitmap对象进入内存,如果图片在mipmap-ldpi或mipmap-mdpi甚至可能需要几百M内存。可以通过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;
}
图片大小是由图片质量和分辨率决定的。例如一张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);
}
可优化(参考自 https://github.com/LuckyJayce/LargeImage):