Android 大图加载

简介

这个项目是一个用于Android预览大图片的图片显示库,可实现原始图片高清显示,专门针对大图片做了很多优化,可以顺畅显示,缩放10多兆的高清图片。效果如下:


原图显示演示

项目地址:https://github.com/kareluo/AndroidPicturePreview

相关技术概述

  • 图片分块加载
    图片的分块加载在地图绘制的情况上最为明显,当想获取一张尺寸很大的图片的某一小块区域时,就用到了图片的分块加载,在Android中BitmapRegionDecoder类的功能就是加载一张图片的指定区域。BitmapRegionDecoder类的使用非常简单,API很少并且一目了然,如下:

    // 创建实例
    mDecoder = BitmapRegionDecoder.newInstance(mFile.getAbsolutePath(), false);
    
    // 获取原图片宽高
    mDecoder.getWidth();
    mDecoder.getHeight();
    
    // 加载(10, 10) - (80, 80) 区域内原始精度的Bitmap对象
    Rect rect = new Rect(10, 10, 80, 80);
    BitmapFactory.Options options = new BitmapFactory.Options();
    options.inSampleSize = 1;
    
    Bitmap bitmap = mDecoder.decodeRegion(rect, options);
    
    // 回收释放Native层内存
    mDecoder.recycle();
    
  • LruCache
    LruCache使用时一般需要继承其,并重写其一些方法,如sizeOf(), entryRemoved()等。其还提供了一个放create()用于get()获取不到时自动创建,更加需要可实现其。
    其中sizeOf()用于处理获取缓存对象的大小,比如缓存Bitmap对象时,可以使用Bitmap的字节数作为Bitmap大小的表示,值得注意的一点是,LruCache不能换成大小会变的对象,sizeOf()对同一个对象始终要返回相同的值,如果非要换成会变动的对象,那么可以让其返回固定值,如下:

    @Override
    protected int sizeOf(Point key, Bitmap value) {
       return value.getRowBytes() * value.getHeight();
    }
    

    entryRemoved()也是一个比较重要的方法,用于回收某个对象时调用,这样当回收Bitmap对象时可以调用Bitmap对象的recycle()方法主动释放Bitmap对象的内存。
    create()方法是当get()获取不到对象时调用,默认实现返回null,根据需要可重写其。

  • 手势处理
    主要用到两个手势处理类,分别是ScaleGestureDetectorGestureDetector,前者用于处理缩放手势,后者用于处理其余手势,如移动,快速滑动,点击,双击,长按等。
    ScaleGestureDetector专门处理缩放手势,其比较重要的方法是onScale(ScaleGestureDetector detector),当缩放时会不停地回调这个方法,需要注意的一点是detector.getScaleFactor()获取到的缩放比例是相对上一次的,不如放大时一般这个值会是1.1, 1.2, 1.1, ....

    @Override
    public boolean onScale(ScaleGestureDetector detector) {
       mIntensifyView.addScale(detector.getScaleFactor(),
               detector.getFocusX(), detector.getFocusY());
       return true;
    }
    

加载流程

图片状态流程图
  • NONE(无状态)
    初始的状态,这个时候的View还没有设置图片资源,NONE状态不会自动转变到其他状态,只有外界设置了图片资源后才会进行下面的状态的转移。

  • SRC(锁定资源)
    当外界设置了图片资源后,View便具有了图片源,这是就到了SRC状态。

  • LOAD(加载宽高)
    到达LOAD状态前,需要加载图片的真实宽高,真实宽高加载完成后就到了LOAD状态,LOAD状态还没有真正加载图片,这是有必要的,如界面不可见,图片不可见时是不需要加载图片的,这里是有待优化的地方。

  • INIT(初始化整图)
    INIT状态会去根据View的显示区域加载一张完整的图片,精度会根据图片和显示区域的尺寸比例计算出一个比较合适的值,如果图片很大,精度就会比较低,如果图片比较小,精度就很高,这张图片是作为底图使用。

  • FREE(ScaleType完成)
    图片显示都会有一个ScaleType,目前只有两种,FIT_CENTER,FIT_AUTO,前者和ImageView的一样,后者的显示方式可以参考微博图片,微信图片的显示方式,对于长图会比较明显,只有这一步做完了才能真正的去显示图片,也就是FREE状态才是用于显示图片的。图片的缩放,移动,快速移动等操作都是在这个状态上做的。

内存缓存

  • LruCache增强
    Android中的LruCache的使用和前面介绍的完全一致,这里使用的时候做了一些处理,实现了一个新的LruCache名为IntensifyCache,比如实现了create(),在获取Bitmap时,如果不存在就加载等,并且添加了两个方法,一个是justGet()用于仅仅获取,不会自动触发create()方法,前面说过get()方法在获取不到时会触发create()方法去创建。另一个方法是alternative()用于提供备选方法,就是在get()获取不到时先不创建,而是先去查找已有的其他缓存是否有可代替的,如果可以找到,那么就返回这个,否则继续走create()方案,这里的设计是针对Bitmap缓存做的,不一定适合其他的缓存。
    上面介绍了两个增加的方法的大概功能,现在介绍下IntensifyImageCache是如何缓存Bitmap的,首先IntensifyImageCache类有个内部类ImageCache是根据Point缓存Bitmap的缓存类,这个类也是继承IntensifyCache的。此类的两个比较重要的方法实现如下:

    @Override
    protected Bitmap create(Point key) {
         BitmapFactory.Options options = new BitmapFactory.Options();
         options.inSampleSize = level;
         Rect rect = blockRect(key.x, key.y, BLOCK_SIZE);
         if (rect.intersect(mOriginalRect)) {
            return mRegionDecoder.decodeRegion(rect, options);
         }
         return null;
    }
    

    create方法,此方法是指定的点获取对应的Bitmap对象,这里所说的点并不是像素点,而是将图片按照某个尺寸划分出来的方格的坐标,后面会有详细点的说明。这里其实不难明白,其实就是加载指定坐标对应的Bitmap对象,level就是精度。只用与图片原始区域有交集时才会加载并返回对应的Bitmap对象。

    另外一个方法是alternative,此方法是提供备选,当获取某精度下的某坐标的Bitmap对象时发现没有,这时会优先去寻找更高精度的对应位置的缓存,一旦发现会直接返回并使用其。代码如下:

    @Override
    protected Bitmap alternative(Point key, Integer level) {
       if (!this.level.equals(level)) {
           Bitmap bitmap = justGet(key);
           if (bitmap != null) {
               return bitmap;
           }
       }
       if (level > 1) {
           ImageCache imageCache = IntensifyImageCache.this.justGet(level >> 1);
           if (imageCache != null) {
               return imageCache.alternative(key, level >> 1);
           }
       }
       return null;
    }
    

    这些都是ImageCache做的事情,那么IntensifyImageCache做了什么呢?IntensifyImageCache是按照精度,缓存了不同的ImageCache,它的键值是精度,值是ImageCache,按照最开始说的,其sizeOf()方法要使用固定返回值,这里使用的是默认值1。并不为其做备选策略。

图片表示

这个过程摸索了很久,最后确定用一个RectF对象,始终表示着真正的图片的边界,需要计算显示的就是与可视区域的交集部分,每次当缩放,滑动等操作时都会去计算并修改RectF对象,之所以使用RectF而不使用Rect想必大家都明白,RectF是浮点型,表示的更加精确,防止图像会有细微的跳跃感。如此一来我的滑动等操作,都可以使用系统提供的ScrollBy等,Fling也会更加简单,显示区域的真正区域是完全分开的,显示时只需计算交集即可,另外也无需关心原点在哪里。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,445评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,889评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,047评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,760评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,745评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,638评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,011评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,669评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,923评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,655评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,740评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,406评论 4 320
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,995评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,961评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,023评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,483评论 2 342

推荐阅读更多精彩内容