簡介
這個項目是一個用于Android預(yù)覽大圖片的圖片顯示庫,可實現(xiàn)原始圖片高清顯示,專門針對大圖片做了很多優(yōu)化粪摘,可以順暢顯示,縮放10多兆的高清圖片苔悦。效果如下:
相關(guān)技術(shù)概述
-
圖片分塊加載
圖片的分塊加載在地圖繪制的情況上最為明顯椎咧,當(dāng)想獲取一張尺寸很大的圖片的某一小塊區(qū)域時,就用到了圖片的分塊加載蟋座,在Android中BitmapRegionDecoder
類的功能就是加載一張圖片的指定區(qū)域脚牍。BitmapRegionDecoder
類的使用非常簡單,API很少并且一目了然券膀,如下:// 創(chuàng)建實例 mDecoder = BitmapRegionDecoder.newInstance(mFile.getAbsolutePath(), false); // 獲取原圖片寬高 mDecoder.getWidth(); mDecoder.getHeight(); // 加載(10, 10) - (80, 80) 區(qū)域內(nèi)原始精度的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層內(nèi)存 mDecoder.recycle();
-
LruCache
LruCache使用時一般需要繼承其,并重寫其一些方法芹彬,如sizeOf()
,entryRemoved()
等。其還提供了一個放create()
用于get()
獲取不到時自動創(chuàng)建会喝,更加需要可實現(xiàn)其会前。
其中sizeOf()用于處理獲取緩存對象的大小,比如緩存Bitmap對象時蔚万,可以使用Bitmap的字節(jié)數(shù)作為Bitmap大小的表示临庇,值得注意的一點是,LruCache不能換成大小會變的對象假夺,sizeOf()
對同一個對象始終要返回相同的值,如果非要換成會變動的對象梧田,那么可以讓其返回固定值,如下:@Override protected int sizeOf(Point key, Bitmap value) { return value.getRowBytes() * value.getHeight(); }
entryRemoved()
也是一個比較重要的方法裁眯,用于回收某個對象時調(diào)用讳癌,這樣當(dāng)回收Bitmap對象時可以調(diào)用Bitmap對象的recycle()
方法主動釋放Bitmap對象的內(nèi)存。
create()
方法是當(dāng)get()
獲取不到對象時調(diào)用逢艘,默認(rèn)實現(xiàn)返回null
骤菠,根據(jù)需要可重寫其。 -
手勢處理
主要用到兩個手勢處理類商乎,分別是ScaleGestureDetector
和GestureDetector
,前者用于處理縮放手勢爬泥,后者用于處理其余手勢,如移動袍啡,快速滑動,點擊蔗牡,雙擊嗅剖,長按等。
ScaleGestureDetector
專門處理縮放手勢信粮,其比較重要的方法是onScale(ScaleGestureDetector detector)
,當(dāng)縮放時會不停地回調(diào)這個方法督惰,需要注意的一點是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(無狀態(tài))
初始的狀態(tài)觉阅,這個時候的View還沒有設(shè)置圖片資源秘车,NONE狀態(tài)不會自動轉(zhuǎn)變到其他狀態(tài),只有外界設(shè)置了圖片資源后才會進(jìn)行下面的狀態(tài)的轉(zhuǎn)移痴柔。SRC(鎖定資源)
當(dāng)外界設(shè)置了圖片資源后疫向,View便具有了圖片源豪嚎,這是就到了SRC狀態(tài)。LOAD(加載寬高)
到達(dá)LOAD狀態(tài)前侈询,需要加載圖片的真實寬高,真實寬高加載完成后就到了LOAD狀態(tài)囊嘉,LOAD狀態(tài)還沒有真正加載圖片,這是有必要的扭粱,如界面不可見,圖片不可見時是不需要加載圖片的蜓堕,這里是有待優(yōu)化的地方博其。INIT(初始化整圖)
INIT狀態(tài)會去根據(jù)View的顯示區(qū)域加載一張完整的圖片,精度會根據(jù)圖片和顯示區(qū)域的尺寸比例計算出一個比較合適的值背伴,如果圖片很大儡率,精度就會比較低,如果圖片比較小儿普,精度就很高货徙,這張圖片是作為底圖使用。FREE(ScaleType完成)
圖片顯示都會有一個ScaleType浪汪,目前只有兩種,F(xiàn)IT_CENTER死遭,F(xiàn)IT_AUTO,前者和ImageView的一樣钉迷,后者的顯示方式可以參考微博圖片钠署,微信圖片的顯示方式,對于長圖會比較明顯谐鼎,只有這一步做完了才能真正的去顯示圖片,也就是FREE狀態(tài)才是用于顯示圖片的身害。圖片的縮放,移動傍菇,快速移動等操作都是在這個狀態(tài)上做的界赔。
內(nèi)存緩存
-
LruCache增強(qiáng)
Android中的LruCache的使用和前面介紹的完全一致,這里使用的時候做了一些處理淮悼,實現(xiàn)了一個新的LruCache名為IntensifyCache,比如實現(xiàn)了create()
见擦,在獲取Bitmap時羹令,如果不存在就加載等,并且添加了兩個方法酒来,一個是justGet()
用于僅僅獲取肪凛,不會自動觸發(fā)create()
方法,前面說過get()
方法在獲取不到時會觸發(fā)create()
方法去創(chuàng)建伟墙。另一個方法是alternative()
用于提供備選方法,就是在get()
獲取不到時先不創(chuàng)建就乓,而是先去查找已有的其他緩存是否有可代替的拱烁,如果可以找到,那么就返回這個守伸,否則繼續(xù)走create()
方案浦妄,這里的設(shè)計是針對Bitmap緩存做的,不一定適合其他的緩存剂娄。
上面介紹了兩個增加的方法的大概功能,現(xiàn)在介紹下IntensifyImageCache是如何緩存Bitmap的和二,首先IntensifyImageCache類有個內(nèi)部類ImageCache是根據(jù)Point緩存Bitmap的緩存類耳胎,這個類也是繼承IntensifyCache的。此類的兩個比較重要的方法實現(xiàn)如下:@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
方法废登,此方法是指定的點獲取對應(yīng)的Bitmap對象郁惜,這里所說的點并不是像素點,而是將圖片按照某個尺寸劃分出來的方格的坐標(biāo)兆蕉,后面會有詳細(xì)點的說明。這里其實不難明白易稠,其實就是加載指定坐標(biāo)對應(yīng)的Bitmap對象包蓝,level就是精度。只用與圖片原始區(qū)域有交集時才會加載并返回對應(yīng)的Bitmap對象衬吆。另外一個方法是
alternative
,此方法是提供備選绳泉,當(dāng)獲取某精度下的某坐標(biāo)的Bitmap對象時發(fā)現(xiàn)沒有逊抡,這時會優(yōu)先去尋找更高精度的對應(yīng)位置的緩存,一旦發(fā)現(xiàn)會直接返回并使用其零酪。代碼如下:@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()
方法要使用固定返回值瓣赂,這里使用的是默認(rèn)值1片拍。并不為其做備選策略煌集。
圖片表示
這個過程摸索了很久,最后確定用一個RectF對象捌省,始終表示著真正的圖片的邊界苫纤,需要計算顯示的就是與可視區(qū)域的交集部分,每次當(dāng)縮放纲缓,滑動等操作時都會去計算并修改RectF對象卷拘,之所以使用RectF而不使用Rect想必大家都明白,RectF是浮點型祝高,表示的更加精確恭金,防止圖像會有細(xì)微的跳躍感。如此一來我的滑動等操作褂策,都可以使用系統(tǒng)提供的ScrollBy等横腿,F(xiàn)ling也會更加簡單,顯示區(qū)域的真正區(qū)域是完全分開的斤寂,顯示時只需計算交集即可耿焊,另外也無需關(guān)心原點在哪里。