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);
    }

可优化(参考自 https://github.com/LuckyJayce/LargeImage):

  • 手势监听滑动可以考虑用Scroller
  • 绘制界面感觉卡顿,可以开启线程提前创建Bitmap

三、参考文章

  1. https://developer.android.com/topic/performance/graphics/load-bitmap
  2. https://blog.csdn.net/lmj623565791/article/details/49300989
  3. https://github.com/LuckyJayce/LargeImage

你可能感兴趣的:(布局,控件混合)