來學習一下Glide強大的圖片變換功能吧
大家好,又到了學習Glide的時間了。前段時間由於項目開發緊張,再加上後來又生病了,所以停更了一個月,不過現在終於又可以恢復正常更新了。今天是這個系列的第五篇文章,在前面四篇文章的當中,我們已經學習了Glide的基本用法、Glide的工作原理和執行流程、Glide的緩存機制、以及Glide的回調機制等內容。如果你能將前面的四篇文章都掌握好了,那麼恭喜你,現在你已經是一名Glide好手了。
如果你還沒有閱讀過前面四篇文章的話,那麼可以點擊後面的鏈接,依次向前閱讀帶你玩轉Glide的回調與監聽。
不過Glide的這個框架的功能實在是太強大了,它所能做的事情遠遠不止於目前我們所學的這些。因此,今天我們就再來學習一個新的功能模塊,並且是一個非常重要的模塊——Glide的圖片變化功能。
一個問題
在正式開始學習Glide的圖片變化功能之前,我們先來看一個問題,這個問題可能有不少人都在使用Glide的時候都遇到過,正好在本篇內容的主題之下我們順帶著將這個問題給解決了。
首先我們嘗試使用Glide來載入一張圖片,圖片URL地址是:
https://www.baidu.com/img/bd_logo1.png
這是百度首頁logo的一張圖片,圖片尺寸是540*258像素。
接下來我們編寫一個非常簡單的布局文件,如下所示:
布局文件中只有一個按鈕和一個用於顯示圖片的ImageView。注意,ImageView的寬和高這裡設置的都是wrap_content。
然後編寫如下的代碼來載入圖片:
這些簡單的代碼對於現在的你而言應該都是小兒科了,相信我也不用再做什麼解釋。現在運行一下程序並點擊載入圖片按鈕,效果如下圖所示。
GIF/1K
圖片是正常載入出來了,不過大家有沒有發現一個問題。百度這張logo圖片的尺寸只有540*258像素,但是我的手機的解析度卻是1080*1920像素,而我們將ImageView的寬高設置的都是wrap_content,那麼圖片的寬度應該只有手機屏幕寬度的一半而已,但是這裡卻充滿了全屏,這是為什麼呢?
如果你之前也被這個問題困擾過,那麼恭喜,本篇文章正是你所需要的。之所以會出現這個現象,就是因為Glide的圖片變換功能所導致的。那麼接下來我們會先分析如何解決這個問題,然後再深入學習Glide圖片變化的更多功能。
稍微對Android有點了解的人應該都知道ImageView有scaleType這個屬性,但是可能大多數人卻不知道,如果在沒有指定scaleType屬性的情況下,ImageView默認的scaleType是什麼?
這個問題如果直接問我,我也答不上來。不過動手才是檢驗真理的唯一標準,想知道答案,自己動手試一下就知道了。
可以看到,我們在onCreate()方法中列印了ImageView默認的scaleType,然後重新運行一下程序,結果如下圖所示:
由此我們可以得知,在沒有明確指定的情況下,ImageView默認的scaleType是FIT_CENTER。
有了這個前提條件,我們就可以繼續去分析Glide的源碼了。當然,本文中的源碼還是建在第二篇源碼分析的基礎之上,還沒有看過這篇文章的朋友,建議先去閱讀Glide系列第二彈,從源碼的角度理解Glide的執行流程。
回顧一下第二篇文章中我們分析過的into()方法,它是在GenericRequestBuilder類當中的,代碼如下所示:
還記得我們當初分析這段代碼的時候,直接跳過前面的所有代碼,直奔最後一行。因為那個時候我們的主要任務是分析Glide的主線執行流程,而不去仔細閱讀它的細節,但是現在我們是時候應該閱讀一下細節了。
可以看到,這裡在第7行會進行一個switch判斷,如果ImageView的scaleType是CENTER_CROP,則會去調用applyCenterCrop()方法,如果scaleType是FIT_CENTER、FIT_START或FIT_END,則會去調用applyFitCenter()方法。這裡的applyCenterCrop()和applyFitCenter()方法其實就是向Glide的載入流程中添加了一個圖片變換操作,具體的源碼我們就不跟進去看了。
那麼現在我們就基本清楚了,由於ImageView默認的scaleType是FIT_CENTER,因此會自動添加一個FitCenter的圖片變換,而在這個圖片變換過程中做了某些操作,導致圖片充滿了全屏。
那麼我們該如何解決這個問題呢?最直白的一種辦法就是看著源碼來改。當ImageView的scaleType是CENTER_CROP、FIT_CENTER、FIT_START或FIT_END時不是會自動添加一個圖片變換操作嗎?那我們把scaleType改成其他值不就可以了。ImageView的scaleType可選值還有CENTER、CENTER_INSIDE、FIT_XY等。這當然是一種解決方案,不過只能說是一種比較笨的解決方案,因為我們為了解決這個問題而去改動了ImageView原有的scaleType,那如果你真的需要ImageView的scaleType為CENTER_CROP或FIT_CENTER時可能就傻眼了。
上面只是我們通過分析源碼得到的一種解決方案,並不推薦大家使用。實際上,Glide給我們提供了專門的API來添加和取消圖片變換,想要解決這個問題只需要使用如下代碼即可:
Glide.with(this) .load(url) .dontTransform() .into(imageView);
可以看到,這裡調用了一個dontTransform()方法,表示讓Glide在載入圖片的過程中不進行圖片變換,這樣剛才調用的applyCenterCrop()、applyFitCenter()就統統無效了。
現在我們重新運行一下代碼,效果如下圖所示:
GIF/1K
這樣圖片就只會佔據半個屏幕的寬度了,說明我們的代碼奏效了。
但是使用dontTransform()方法存在著一個問題,就是調用這個方法之後,所有的圖片變換操作就全部失效了,那如果我有一些圖片變換操作是必須要執行的該怎麼辦呢?不用擔心,總歸是有辦法的,這種情況下我們只需要藉助override()方法強制將圖片尺寸指定成原始大小就可以了,代碼如下所示:
Glide.with(this) .load(url) .override(Target.SIZE_ORIGINAL,
Target.SIZE_ORIGINAL) .into(imageView);
通過override()方法將圖片的寬和高都指定成Target.SIZE_ORIGINAL,問題同樣被解決了。程序的最終運行結果和上圖是完全一樣的,我就不再重新截圖了。
由此我們可以看出,之所以會出現這個問題,和Glide的圖片變換功能是撇不開關係的。那麼也是通過這個問題,我們對Glide的圖片變換有了一個最基本的認識。接下來,就讓我們正式開始進入本篇文章的正題吧。
圖片變換的基本用法
顧名思義,圖片變換的意思就是說,Glide從載入了原始圖片到最終展示給用戶之前,又進行了一些變換處理,從而能夠實現一些更加豐富的圖片效果,如圖片圓角化、圓形化、模糊化等等。
添加圖片變換的用法非常簡單,我們只需要調用transform()方法,並將想要執行的圖片變換操作作為參數傳入transform()方法即可,如下所示:
Glide.with(this) .load(url) .transform(...) .into(imageView);
至於具體要進行什麼樣的圖片變換操作,這個通常都是需要我們自己來寫的。不過Glide已經內置了兩種圖片變換操作,我們可以直接拿來使用,一個是CenterCrop,一個是FitCenter。
但這兩種內置的圖片變換操作其實都不需要使用transform()方法,Glide為了方便我們使用直接提供了現成的API:
Glide.with(this) .load(url) .centerCrop() .into(imageView);
Glide.with(this) .load(url) .fitCenter() .into(imageView);
當然,centerCrop()和fitCenter()方法其實也只是對transform()方法進行了一層封裝而已,它們背後的源碼仍然還是藉助transform()方法來實現的,如下所示:
那麼這兩種內置的圖片變換操作到底能實現什麼樣的效果呢?FitCenter的效果其實剛才我們已經見識過了,就是會將圖片按照原始的長寬比充滿全屏。那麼CenterCrop又是什麼樣的效果呢?我們來動手試一下就知道了。
為了讓效果更加明顯,這裡我就不使用百度首頁的Logo圖了,而是換成必應首頁的一張美圖。在不應用任何圖片變換的情況下,使用Glide載入必應這張圖片效果如下所示。
現在我們添加一個CenterCrop的圖片變換操作,代碼如下:
Glide.with(this) .load(url) .centerCrop() .into(imageView);
重新運行一下程序並點擊載入圖片按鈕,效果如下圖所示。
可以看到,現在展示的圖片是對原圖的中心區域進行裁剪後得到的圖片。
另外,centerCrop()方法還可以配合override()方法來實現更加豐富的效果,比如指定圖片裁剪的比例:
Glide.with(this) .load(url) .override(500,500) .centerCrop() .into(imageView);
可以看到,這裡我們將圖片的尺寸設定為500*500像素,那麼裁剪的比例也就變成1:1了,現在重新運行一下程序,效果如下圖所示。
這樣我們就把Glide內置的圖片變換介面的用法都掌握了。不過不得不說,Glide內置的圖片變換介面功能十分單一且有限,完全沒有辦法滿足我們平時的開發需求。因此,掌握自定義圖片變換功能就顯得尤為重要了。
不過,在正式開始學習自定義圖片變換功能之前,我們先來探究一下CenterCrop這種圖片變換的源碼,理解了它的源碼我們再來進行自定義圖片變換就能更加得心應手了。
源碼分析
那麼就話不多說,我們直接打開CenterCrop類來看一下它的源碼吧,如下所示:
這段代碼並不長,但是我還是要划下重點,這樣大家看起來的時候會更加輕鬆。
首先,CenterCrop是繼承自BitmapTransformation的,這個是重中之重,因為整個圖片變換功能都是建立在這個繼承結構基礎上的。
接下來CenterCrop中最重要的就是transform()方法,其他的方法我們可以暫時忽略。transform()方法中有四個參數,每一個都很重要,我們來一一解讀下。第一個參數pool,這個是Glide中的一個Bitmap緩存池,用於對Bitmap對象進行重用,否則每次圖片變換都重新創建Bitmap對象將會非常消耗內存。第二個參數toTransform,這個是原始圖片的Bitmap對象,我們就是要對它來進行圖片變換。第三和第四個參數比較簡單,分別代表圖片變換後的寬度和高度,其實也就是override()方法中傳入的寬和高的值了。
下面我們來看一下transform()方法的細節,首先第一行就從Bitmap緩存池中嘗試獲取一個可重用的Bitmap對象,然後把這個對象連同toTransform、outWidth、outHeight參數一起傳入到了TransformationUtils.centerCrop()方法當中。那麼我們就跟進去來看一下這個方法的源碼,如下所示:
這段代碼就是整個圖片變換功能的核心代碼了。可以看到,第5-9行主要是先做了一些校驗,如果原圖為空,或者原圖的尺寸和目標裁剪尺寸相同,那麼就放棄裁剪。接下來第11-22行是通過數學計算來算出畫布的縮放的比例以及偏移值。第24-29行是判斷緩存池中取出的Bitmap對象是否為空,如果不為空就可以直接使用,如果為空則要創建一個新的Bitmap對象。第32行是將原圖Bitmap對象的alpha值複製到裁剪Bitmap對象上面。最後第34-37行是裁剪Bitmap對象進行繪製,並將最終的結果進行返回。全部的邏輯就是這樣,總體來說還是比較簡單的,可能也就是數學計算那邊需要稍微動下腦筋。
那麼現在得到了裁剪後的Bitmap對象,我們再回到CenterCrop當中,你會看到,在最終返回這個Bitmap對象之前,還會嘗試將復用的Bitmap對象重新放回到緩存池當中,以便下次繼續使用。
好的,這樣我們就將CenterCrop圖片變換的工作原理完整地分析了一遍,FitCenter的源碼也是基本類似的,這裡就不再重複分析了。了解了這些內容之後,接下來我們就可以開始學習自定義圖片變換功能了。
自定義圖片變換
Glide給我們定製好了一個圖片變換的框架,大致的流程是我們可以獲取到原始的圖片,然後對圖片進行變換,再將變換完成後的圖片返回給Glide,最終由Glide將圖片顯示出來。理論上,在對圖片進行變換這個步驟中我們可以進行任何的操作,你想對圖片怎麼樣都可以。包括圓角化、圓形化、黑白化、模糊化等等,甚至你將原圖片完全替換成另外一張圖都是可以的。
但是這裡顯然我不可能向大家演示所有圖片變換的可能,圖片變換的可能性也是無限的。因此這裡我們就選擇一種常用的圖片變換效果來進行自定義吧——對圖片進行圓形化變換。
圖片圓形化的功能現在在手機應用中非常常見,比如手機QQ就會將用戶的頭像進行圓形化變換,從而使得界面變得更加好看。
自定義圖片變換功能的實現邏輯比較固定,我們剛才看過CenterCrop的源碼之後,相信你已經基本了解整個自定義的過程了。其實就是自定義一個類讓它繼承自BitmapTransformation ,然後重寫transform()方法,並在這裡去實現具體的圖片變換邏輯就可以了。一個空的圖片變換實現大概如下所示:
這裡有一點需要注意,就是getId()方法中要求返回一個唯一的字元串來作為id,以和其他的圖片變換做區分。通常情況下,我們直接返回當前類的完整類名就可以了。
另外,這裡我們選擇繼承BitmapTransformation還有一個限制,就是只能對靜態圖進行圖片變換。當然,這已經足夠覆蓋日常95%以上的開發需求了。如果你有特殊的需求要對GIF圖進行圖片變換,那就得去自己實現Transformation介面才可以了。不過這個就非常複雜了,不在我們今天的討論範圍。
好了,那麼我們繼續實現對圖片進行圓形化變換的功能,接下來只需要在transform()方法中去做具體的邏輯實現就可以了,代碼如下所示:
下面我來對transform()方法中的邏輯做下簡單的解釋。首先第18行先算出原圖寬度和高度中較小的值,因為對圖片進行圓形化變換肯定要以較小的那個值作為直徑來進行裁剪。第20-26行則和剛才一樣,從Bitmap緩存池中嘗試獲取一個Bitmap對象來進行重用,如果沒有可重用的Bitmap對象的話就創建一個。第28-41行是具體進行圓形化變換的部分,這裡算出了畫布的偏移值,並且根據剛才得到的直徑算出半徑來進行畫圓。最後,嘗試將復用的Bitmap對象重新放回到緩存池當中,並將圓形化變換後的Bitmap對象進行返回。
這樣,一個自定義圖片變換的功能就寫好了,那麼現在我們就來嘗試使用一下它吧。使用方法非常簡單,剛才已經介紹過了,就是把這個自定義圖片變換的實例傳入到transform()方法中即可,如下所示:
Glide.with(this) .load(url) .transform(newCircleCrop(this)) .into(imageView);
現在我們重新運行一下程序,效果如下圖所示。
更多圖片變換功能
雖說Glide的圖片變換功能框架已經很強大了,使得我們可以輕鬆地自定義圖片變換效果,但是如果每一種圖片變換都要我們自己去寫還是蠻吃力的。事實上,確實也沒有必要完全靠自己去實現各種各樣的圖片變換效果,因為大多數的圖片變換都是比較通用的,各個項目會用到的效果都差不多,我們每一個都自己去重新實現無異於重複造輪子。
也正是因此,網上出現了很多Glide的圖片變換開源庫,其中做的最出色的應該要數glide-transformations這個庫了。它實現了很多通用的圖片變換效果,如裁剪變換、顏色變換、模糊變換等等,使得我們可以非常輕鬆地進行各種各樣的圖片變換。
glide-transformations的項目主頁地址是:https://github.com/wasabeef/glide-transformations。
下面我們就來體驗一下這個庫的強大功能吧。首先需要將這個庫引入到我們的項目當中,在app/build.gradle文件當中添加如下依賴:
dependencies { compile jp.wasabeef:glide-transformations:2.0.2
}
現在如果我想對圖片進行模糊化處理,那麼就可以使用glide-transformations庫中的BlurTransformation這個類,代碼如下所示:
Glide.with(this) .load(url) .bitmapTransform(newBlurTransformation(this)) .into(imageView);
注意這裡我們調用的是bitmapTransform()方法而不是transform()方法,因為glide-transformations庫都是專門針對靜態圖片變換來進行設計的。現在重新運行一下程度,效果如下圖所示。
沒錯,我們就這樣輕鬆地實現模糊化的效果了。
接下來我們再試一下圖片黑白化的效果,使用的是GrayscaleTransformation這個類,代碼如下所示:
Glide.with(this) .load(url) .bitmapTransform(newGrayscaleTransformation(this)) .into(imageView);
現在重新運行一下程度,效果如下圖所示。
而且我們還可以將多個圖片變換效果組合在一起使用,比如同時執行模糊化和黑白化的變換:
Glide.with(this) .load(url) .bitmapTransform(newBlurTransformation(this),
newGrayscaleTransformation(this)) .into(imageView);
可以看到,同時執行多種圖片變換的時候,只需要將它們都傳入到bitmapTransform()方法中即可。現在重新運行一下程序,效果如下圖所示。
當然,這些只是glide-transformations庫的一小部分功能而已,更多的圖片變換效果你可以到它的GitHub項目主頁去學習,所有變換的用法都是這麼簡單哦。
好了,那麼今天的文章就到這裡了,相信大家的收穫都很多吧。下篇文章中我們會繼續深入探究Glide,學習一下模塊替換的功能,敬請期待。
更多
每天學習累了,看些搞笑的段子放鬆一下吧。關注最具娛樂精神的公眾號,每天都有好心情。
如果你有好的技術文章想和大家分享,歡迎向我的公眾號投稿,投稿具體細節請在公眾號主頁點擊「投稿」菜單查看。
歡迎長按下圖 -> 識別圖中二維碼或者掃一掃關注我的公眾號:
TAG:郭霖 |
※Glide 系列(七)Glide圖像變換
※世界第一件可以自由變換的多功能外套,City Jackat
※Facebook開源DeepFocus演算法,為VR提供逼真的焦點變換渲染
※范丞丞的心形髮型有創意,ninepercent全員髮型變換多,你喜歡哪一個
※Angelababy時尚大片,她變換多套不同風格的造型大秀文藝色彩!
※隨光線變換色彩!Air Jordan 11 「Iridescent」 實物美圖搶先看
※僅僅通過變換顏色就能持續火熱,Converse 與「這朵花」的聯乘憑什麼?
※opencv+python Hough變換的基本原理
※58 張圖,手把手教會你 Simscape Multibody 物理建模與剛體變換
※美國Kickstarter眾籌爆款時鐘,可隨天色而變換顏色,簡約卻高級
※楊清檸為凸顯Lolita風,特意變換髮型拍照,slay幼稚園
※學會這些小技巧,從此你的Bob Hair也可以天天變換不同造型!
※基於Arnold變換的改進騎士巡遊圖像加密演算法
※CVPR最新醫學影像AI論文:利用學習圖像變換進行數據增強
※變換莫測的流光溢彩 vivo X27 Pro美圖鑑賞
※Minitab:Box-Cox變換後的正態分布能力分析
※Buck變換器近遠端反饋的模擬分析與應用
※yamy卸掉「5厘米」眼線,厭世可愛自由變換!wink厲害了
※CVPR 2018 Spotlight論文:U-Net,按條件獨立變換目標外觀和形狀
※Buck變換器在射流清洗設備電源中的應用