最近公司一個需求拴竹,加載一張超大圖悟衩,允許手勢放大縮小以及左右滑動查看。第一想法是利用inSampleSize
縮小圖片分辨率栓拜,但是效果不大理想座泳,因為放大后圖片會變模糊。產(chǎn)品要求放大顯示時幕与,圖片清晰度不能降低挑势,查了下Btimap相關(guān)的api,發(fā)現(xiàn)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(); //顯示區(qū)域?qū)挾? int ivHeight = getMeasuredHeight(); //顯示區(qū)域高度
inSampleSize = 1; //默認不壓縮
//下面為計算初始狀態(tài)凫碌,圖片完全展示時的壓縮比例
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創(chuàng)建
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(); //顯示的寬度,也就是當(dāng)前顯示View的寬度
int height = getMeasuredHeight(); //同上
//注:rectF為手勢縮放以及平移后,整個圖片的矩陣位置枉层,具體邏輯就不貼出來了
int scale = (int) (rectF.width() / width); //圖片手勢縮放的比例
//下面為根據(jù)當(dāng)前手勢縮放比例泉褐,重新計算圖片繪制時的壓縮比例
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); //屏幕的繪制區(qū)域,即當(dāng)前顯示View的整個大小
if(bitmapRegionDecoder != null){
drawRect.set(left, top, right, bottom); //原始圖片需要截取顯示的區(qū)域
Bitmap bitmap = bitmapRegionDecoder.decodeRegion(drawRect, options); //截取需要顯示的圖片區(qū)域
canvas.drawBitmap(bitmap, null, destRect, null); //繪制
}
}
發(fā)現(xiàn)問題
查看運行效果鸟蜡,發(fā)現(xiàn)放大后圖片未模糊膜赃,問題解決。但是引發(fā)一個新的問題揉忘,縮放或滑動的時候會比較卡頓跳座。分析了下,原來是在onDraw
的時候頻繁創(chuàng)建Bitmap
導(dǎo)致泣矛。
Bitmap bitmap = bitmapRegionDecoder.decodeRegion(drawRect, options); //截取需要顯示的圖片區(qū)域
解決問題
方案一
考慮創(chuàng)建Bitmap時疲眷,將Bitmap創(chuàng)建比顯示區(qū)域稍微大一些,這樣您朽,在小幅度滑動或縮放時狂丝,就不必重新創(chuàng)建Bitmap。通過減少Bitmap創(chuàng)建次數(shù)哗总,降低卡頓几颜。
如下圖,黃色矩形為顯示區(qū)域讯屈,紅色矩形為截取的圖片蛋哭,在小幅度滑動或縮放時,圖片依然可以正常顯示涮母,不用重新創(chuàng)建谆趾。
@Override
protected void onDraw(Canvas canvas) {
...
if(lastSampleSize == currentSampleSize && lastDrawBitmap != null && isHitRect(drawRect, lastBitmapRect)){
//當(dāng)圖片像素壓縮比例未變,且上次創(chuàng)建的圖片依然可以覆蓋顯示區(qū)域時叛本,直接使用上次緩存的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){
//創(chuàng)建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);
}
}
運行后,效果不錯炮赦,整體流暢怜跑,只在重新創(chuàng)建Bitmap時卡頓一下,可以接受吠勘。
方案二
將圖片分割為一個個小矩形性芬,在滑動或縮放后,在緩存中查找顯示區(qū)域?qū)?yīng)的小矩形Bitmap剧防,如果緩存中沒有植锉,直接使用模糊版的Bitmap占位,再在子線程中創(chuàng)建此時需要的小矩形bitmap峭拘,創(chuàng)建完成后俊庇,緩存起來狮暑,然后將其刷新到畫面上。