Android 笔记 ImageView 显示大图遇到的问题 以及 无损显示大图 的解决方案

ImageView

制作一个显示图片的activity,类似相册那样的,可以移动图片,多点触控的时候放大缩小图片等功能

一般就是用BitmapFactory来解码bitmap,然后设置imageView.setImageBitmap()
但是遇到了一个问题,就是图片过大的时候(也没多大,也就是手机摄像头拍的照片,4128x3096,13M像素),真机运行会显示空白,什么都没有
但是模拟器运行正常,就是有一点卡。

在网上搜索的时候看到有一些解释
1,因为图片太大导致的OOM(out of memory)错误
在程序中设置try-catch并没有抓到这个错误,而且debug发现,在BitmapFactory解码图片以后,可以获取到Bitmap的信息,说明也许没有出现这个错误,图片的内存占用仍然在可取范围内

2,需要设置解码的option参数,inSampleSize
这个参数的意思是,把图像的长/宽缩小,再生成Bitmap,inSampleSize的数值是1,2,4这样的比例,然后长/宽就除以这个数值,那么总像素数就除以这个数值的平方

这样的话,内存占用就小了。实际测试的时候,4128X 3096的照片可以显示,内存占用的确小了很多。但是图像放大以后发现果然分辨率也小了。虽然放的不是很大的时候看起来没区别,但是更希望有个解决方案能显示原图的分辨率

而且,快图浏览这个app可以完美显示原分辨率的图像,说明不需要减小分辨率也可以做到正常显示。

3,又发现一个现象,就是长图也无法显示。
比如一个6000X1000的图,总像素6M,内存占用也很小,但是也无法显示。这证明我在第一条里面的推测是正确的,没有出现OOM,解码的时候正常,但是显示的时候错误。

在网上查找发现是这么一个问题,Bitmap too large to be uploaded into a texture
这个信息并不会出现在错误提示里,所以无法catch,只能在logcat里面看到一个warning
但是我用真机测试的时候还没有找到显示Log的方法(网上找到的方法都无效),而模拟器测试又正常,所以没看到这个warning。

Bitmap too large to be uploaded into a texture (5312x2988, max=4096x4096)
有一些网上的信息是这样显示的,说明最大支持的就是4096x4096。
然后我发现,手机里面有一些正好4096像素宽的图像可以正常显示,大一点的比如4128的就不行,我觉得这个解释才是我这个问题的最终解释。

也就是说,是因为图像在显示的时候,有一个最大边长像素数,我的手机的限制是4096,网上也有其他的情况比如2048等等。

解决方案

1 网上提示说可以关闭硬件加速
在manifest文件里面设置 hardwareAcceleration = “false”,就关闭了硬件加速
没错,这样是可以加载长图了,但是APP变卡了,体验非常不好。舍弃

2 用切割法
就说说,每次显示在imageView中的图不是原图的bitmap,通过一个方法
Bitmap.createBitmap(bitmap,x,y,width,height)创建一个bitmap,长宽符合屏幕要求,这样就不会超过像素限制。实际操作以后发现,的确可以显示大图,但是效果也不好,拖动的时候帧数比较低,比上一条的那种好点,但是没有达到最好的要求(就是像快图浏览里面那样的),即便手机自带的相册app也比这个流畅一些。

3,找了半天找到一个github上的项目,运行以后发现完美达到要求
Subsampling Scale Image View
这个解决方案是这样的,其实也是切割法,但是跟上一条不同的是,需要定义一个自定义的VIEW,自己写里面的onDraw方法,绘制bitmap
当然不要一次性画出来,而是根据情况将图切分为能够绘制出来的块。比如现在很多设备的限制是4096X4096,以前貌似是2048x2048,就根据情况切分为最大这样的块,然后绘制每个块,把块拼接起来,就跟原来的图一样了。

变换的时候,在onDraw里面设置matrix canvas.concat(matrix),然后再drawBitmap(...)
这样,每次绘图的时候就会根据matrix来变换了。当然,也可以直接用带matrix参数的drawBitmap。

这就是基本原理。还有一个,就是前面提到的inSampleSize。在不显示大图的情况下,可以设置inSampleSize来缩小图的比例,可以节省内存提高处理速度

补充

实际操作中我发现一个问题,就是设置canvas.concat(matrix)以后,实际变换的是整个canvas的坐标系。

也就是说,变换matrix以后,画布的中心点已经不再是屏幕中心了。这时候如果在原来的坐标画另外一个图形,会发现画在了其他位置,正好和画布的移动吻合,说明是画布移动了,而不是画布上的图像。

所以,设置全局的matrx以后会带来一点问题,就是坐标系也会移动。

找到一个解决办法
不要设置全局的matrix canvas.concat()
改为:

bitmapMatrix.reset();
bitmapMatrix.postTranslate(block.left,block.top);
bitmapMatrix.postConcat(mMatrix);
canvas.drawBitmap(
     block.bitmap,
     bitmapMatrix,
     mPaint);

其中bitmapMatrix是一个单独的matrix
在每次绘图之前,把这个matrix重置,然后添加一个偏移量,就是第二行
再添加全局的matrix,就是第三行。注意第二行和第三行顺序不能反了,实际测试中发现反了以后会显示错误。
这样的话,就不是改变全局坐标,canvas没有变动,变动的是bitmap。

你可能感兴趣的:(Android)