1. 程式人生 > >Android Bitmap載入大圖片

Android Bitmap載入大圖片

在相機拍攝畫素越來越高的現在,高解析度的大圖已經很常見,手機載入高清大圖功能基本已成日常需要。但是,由於移動裝置本身記憶體和解析度的限制,通常會先載入縮圖然後根據需要展示大圖內容。

一、載入縮圖

1.讀取圖片大小和型別

BitmapFactory 提供了 decodeByteArraydecodeStreamdecodeFiledecodeResource 等方法建立一個Bitmap物件。通過設定BitmapFactory.OptionsinJustDecodeBounds 為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; }
2.獲取inSampleSize

圖片大小是由圖片質量和解析度決定的。例如一張1280x720的ARGB_8888RGB_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

三、參考文章