Android 中图片的加载是个很头疼的问题,不过还好我们有Glide(逃跑脸..)
不过当你有要显示一张远超手机尺寸的超大图片的需求时,Glide也帮不了你,我现在慌得一B(躺草地)
为什么慌呢,因为长这样
可以看到,首先显示上有问题,其次整张图片加载到了内存中,我们用 Android Profiler 抓一下看看占了多少内存
我滴乖乖,占了快 34M,还好我手机分配给应用的内存够大。那么要如何解决这两个问题呢?且看分解:
一.图片加载占用内存的计算
影响图片在内存中大小的有三个元素:
1.图片原始宽高
2.图片的色彩空间
3.图片的缩放比
(1)图片原始宽高:它们的乘积代表图片的总像素点数
(2)图片的色彩空间:每个像素点的信息,占用多少字节,比如 Bitmap.Config.ARGB8888 代表每个色彩通道占8bit位 总共就是 4个字节,可以在 BitmapFactory.Options 的 inPreferredConfig 属性进行调节,常用的还有 Bitmap.Config.RGB565
(3)图片的缩放比:对图片原始宽高的缩放,影响是次方级的,因为分别作用在了宽和高上。
它对应的设置在 BitmapFactory.Options 的 inSampleSize 属性,代表采样率,默认为1,必须大于1且为2的倍数
比如设置为 4 ,则图片的宽高都将变为原始的 1/4 ,那么总像素点数就变为了原始的 1/16
综上总结的计算公式为:
图片占用内存= (原始宽 * 缩放比) * (原始高 * 缩放比) * 色彩空间
我们算一下刚才的图进行验证一下,妹子图宽 690px 高12287px 直接展示时 缩放比 inSampleSize =1 色彩空间ARGB8888
所以 内存= 690 * 1 * 12287 * 1 * 4 = 33912120 和我们图中抓的基本一致
平时在使用Glide进行加载图片时,框架里帮我们处理了缩放,Glide默认会加载并缓存具体尺寸的图片,同时3.x版本的Glide默认使用RGB565的颜色通道,这些都会帮助我们节省内存
二.图片分区域加载
既然我们一个屏幕展示不下这张图,那么我们就显示一部分,Android 中已经提供了 解码图片部分区域的类 BitmapRegionDecoder,使用起来也很简单
它提供了一系列静态方法构造实例
拿到实例后 通过 #decodeRegion() 方法,传入一个 Rect 和 一个BitmapFactory.Options 参数 即可解码出一张我们要的图片解码区域就是我们 Rect 指定的范围,拿到 Bitmap 后当然可以为所欲为了
三.手势检测
我们已经能够展示大图的部分区域了,那么势必需要提供手势操作让用户滑动或点击来对图片加载的区域或大小进行更新也就是需要自定义控件重写 onTouchEvent方法进行处理
这里可以参考洋神的做法:https://blog.csdn.net/lmj623565791/article/details/49300989/
将手势的处理交给 MoveGestureDetector 然后每次滑动完在 onDraw 里更新 解码的区域 Rect
但是有两个问题,当你将洋神的代码跑起来后,在 7.0 以上的手机上会发现图划不动,并且得到一个日志:
D/skia: --- SkAndroidCodec::NewFromStream returned null
经Google 解决了问题:https://stackoverflow.com/questions/39316069/bitmapfactory-decodestream-from-assets-returns-null-on-android-7
大致就是 Google改了 BitmapFactory.cpp的代码,我们需要在两次decode 之间将 流重置一下 如下:洋神的 LargeImageView.java
第二个问题就是跑起来你会发现滑动起来很卡,体验很差,因为动一动就一直在重绘。
解决方法就是不要继承View ,我看世界地图那个项目是继承自 SurfaceView的,将绘制放到单独的线程
四.开源项目解决方案
1.这个作者加了手势缩放,双击等的处理,同时对显示区域进行了缓存,实测很棒
https://github.com/kareluo/IntensifyImageView
2.一个N年前star的库,但一直还没用过
https://github.com/davemorrissey/subsampling-scale-image-view
五.另辟蹊径的方式
使用WebView 进行展示,思路来自N年前听说有个开源第三方微博客户端是这么搞的
具体参考:https://blog.csdn.net/android_zhengyongbo/article/details/70225377