最近公司一个需求,加载一张超大图,允许手势放大缩小以及左右滑动查看。第一想法是利用inSampleSize
缩小图片分辨率,但是效果不大理想,因为放大后图片会变模糊。产品要求放大显示时,图片清晰度不能降低,查了下Btimap相关的api,发现BitmapRegionDecoder
能够满足需求。
BitmapRegionDecoder
方案找到了,直接搞起
首先解析图片,计算出图片完全展示时的压缩比例,并初始化BitmapRegionDecoder
public void setImageFile(File file){
try {
InputStream inputStream = new FileInputStream(file);
//获得图片的宽、高
BitmapFactory.Options tmpOptions = new BitmapFactory.Options();
tmpOptions.inJustDecodeBounds = true;
BitmapFactory.decodeStream(inputStream, null, tmpOptions);
srcWidth = tmpOptions.outWidth; //原始图片宽度
srcHeight = tmpOptions.outHeight; //原始图片高度
int ivWidth = getMeasuredWidth(); //显示区域宽度
int ivHeight = getMeasuredHeight(); //显示区域高度
inSampleSize = 1; //默认不压缩
//下面为计算初始状态,图片完全展示时的压缩比例
if(ivWidth > 0 && ivHeight > 0){
if (srcHeight > srcWidth && srcHeight>ivHeight) {
while (srcHeight/inSampleSize > ivHeight){
inSampleSize = inSampleSize * 2;
}
}
else if(srcHeight<=srcWidth && srcWidth>ivWidth){
while (srcWidth/inSampleSize > ivWidth){
inSampleSize = inSampleSize * 2;
}
}
}
//BitmapRegionDecoder创建
bitmapRegionDecoder = BitmapRegionDecoder.newInstance(file.getAbsolutePath(), false);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
手势操作代码忽略不写,直接上绘制图片的代码。
重写onDraw
方法,注意super.onDraw(canvas);
需要删掉
@Override
protected void onDraw(Canvas canvas) {
int width = getMeasuredWidth(); //显示的宽度,也就是当前显示View的宽度
int height = getMeasuredHeight(); //同上
//注:rectF为手势缩放以及平移后,整个图片的矩阵位置,具体逻辑就不贴出来了
int scale = (int) (rectF.width() / width); //图片手势缩放的比例
//下面为根据当前手势缩放比例,重新计算图片绘制时的压缩比例
int currentSampleSize = inSampleSize;
while (scale/2 >= 1){
currentSampleSize = currentSampleSize/2;
scale = currentSampleSize / 2;
}
if(currentSampleSize < 1){
currentSampleSize = 1;
}
options.inSampleSize = currentSampleSize;
int left = (int) (-rectF.left/rectF.width() * srcWidth);
int top = (int) (-rectF.top/rectF.height() * srcHeight);
int right = (int) ((width - rectF.left)/rectF.width() *srcWidth);
int bottom = (int) ((height - rectF.top)/rectF.height() * srcHeight);
destRect.set(0, 0, width, height); //屏幕的绘制区域,即当前显示View的整个大小
if(bitmapRegionDecoder != null){
drawRect.set(left, top, right, bottom); //原始图片需要截取显示的区域
Bitmap bitmap = bitmapRegionDecoder.decodeRegion(drawRect, options); //截取需要显示的图片区域
canvas.drawBitmap(bitmap, null, destRect, null); //绘制
}
}
发现问题
查看运行效果,发现放大后图片未模糊,问题解决。但是引发一个新的问题,缩放或滑动的时候会比较卡顿。分析了下,原来是在onDraw
的时候频繁创建Bitmap
导致。
Bitmap bitmap = bitmapRegionDecoder.decodeRegion(drawRect, options); //截取需要显示的图片区域
解决问题
方案一
考虑创建Bitmap时,将Bitmap创建比显示区域稍微大一些,这样,在小幅度滑动或缩放时,就不必重新创建Bitmap。通过减少Bitmap创建次数,降低卡顿。
如下图,黄色矩形为显示区域,红色矩形为截取的图片,在小幅度滑动或缩放时,图片依然可以正常显示,不用重新创建。
@Override
protected void onDraw(Canvas canvas) {
...
if(lastSampleSize == currentSampleSize && lastDrawBitmap != null && isHitRect(drawRect, lastBitmapRect)){
//当图片像素压缩比例未变,且上次创建的图片依然可以覆盖显示区域时,直接使用上次缓存的Bitmap
float bitmapScale = (float)(lastDrawBitmap.getWidth()) / lastBitmapRect.width();
int srcLeft = (int) ((drawRect.left-lastBitmapRect.left) * bitmapScale);
int srcTop = (int) ((drawRect.top-lastBitmapRect.top) * bitmapScale);
int srcRight = (int) ((drawRect.right-lastBitmapRect.left) * bitmapScale);
int srcBottom = (int) ((drawRect.bottom-lastBitmapRect.top) * bitmapScale);
srcRect.set(srcLeft, srcTop, srcRight, srcBottom);
canvas.drawBitmap(lastDrawBitmap, srcRect, destRect, null);
} else if(bitmapRegionDecoder != null){
//创建Bitmap,左右多截取1/4冗余
lastSampleSize = currentSampleSize;
lastBitmapRect.set(left-drawRect.width()/4, top-drawRect.height()/4, right+drawRect.width()/4, bottom+drawRect.height()/4);
// lastBitmapRect.set(left, top, right, bottom);
lastDrawBitmap = bitmapRegionDecoder.decodeRegion(lastBitmapRect, options);
float bitmapScale = (float)(lastDrawBitmap.getWidth()) / lastBitmapRect.width();
int srcLeft = (int) ((drawRect.left-lastBitmapRect.left) * bitmapScale);
int srcTop = (int) ((drawRect.top-lastBitmapRect.top) * bitmapScale);
int srcRight = (int) ((drawRect.right-lastBitmapRect.left) * bitmapScale);
int srcBottom = (int) ((drawRect.bottom-lastBitmapRect.top) * bitmapScale);
srcRect.set(srcLeft, srcTop, srcRight, srcBottom);
canvas.drawBitmap(lastDrawBitmap, srcRect, destRect, null);
}
}
运行后,效果不错,整体流畅,只在重新创建Bitmap时卡顿一下,可以接受。
方案二
将图片分割为一个个小矩形,在滑动或缩放后,在缓存中查找显示区域对应的小矩形Bitmap,如果缓存中没有,直接使用模糊版的Bitmap占位,再在子线程中创建此时需要的小矩形bitmap,创建完成后,缓存起来,然后将其刷新到画面上。