如何用10行程式碼讓app全域性置灰

蘋果味的少年發表於2020-04-07

內容概要

前段時間由於新冠肺炎特別嚴重,政府規定今年的4月4號為悼念日,所有網際網路專案能置灰的要跟隨置灰處理。我們可以看到在京東、百度等部分app中都有置灰的功能。如果是在網頁上的話,只需要一句程式碼就可以搞定了,但是app裡實現可能有些同學會感覺迷茫。今天筆者也跟上潮流,給大家分享一篇如何在app中實現全域性置灰吧,沒有這個需求的朋友們也可以學習探討一下思路,希望可以幫到大家!

一 、如何實現頁面灰度化

實現灰度化的思路應該從Paint出發,因為系統是通過Paint將內容繪製到介面上的,如果能找到Paint相關的設定方法,那就再也合適不過了。自定義View做得多的同學可能知道Paint中可以設定ColorMatrix,以下是其原始碼,從原始碼的註釋我們可以看到如果將setSaturation的sat引數設定為0就代表灰度模式。

如何用10行程式碼讓app全域性置灰

OK,說幹就幹,我們自定義一個圖片控制元件,修改其Paint中的Matrix屬性試試。首先初始化ColorMatrix物件,然後按照原始碼中的說法,將setSaturation引數的值設定為0,接下來將該ColorMatrix設定到Paint中,最後再onDraw方法中使用剛剛的Paint物件,程式碼大致如下:

如何用10行程式碼讓app全域性置灰

以上是在ImageView中實現灰度化,那麼文字的TextView其實也是一樣的,因為本質都是使用Paint進行的繪製,所以可以直接將上面程式碼拷貝到自定義TextView中。

接下來我們在頁面中使用上面自定義好的2個View,我們執行一下,看一下效果。不出所以然,文字和圖片都變成了灰色,具體執行結果如下圖:

如何用10行程式碼讓app全域性置灰

效果是實現了,又有一個新的問題擺在眼前。在專案裡數以萬計的ImageView和TextView,總不可能一個一個來替換吧,這樣做要換到猴年馬月去?

二、如何提升替換效率

從上面TextView、ImageView二者的置灰實現沒有任何區別,我們可以猜測是不是所有的View都能給置灰呢,那麼ViewGroup作為一個特殊的View是否可以置灰呢?接下來我們來驗證一下這個猜想,自定義一個RelativeLayout,還是之前的程式碼,需要注意的是這裡需要複寫dispatchDraw方法。關於onDraw方法和dispatchDraw方法的差別這裡也稍微解釋一下,ViewGroup容器元件的繪製,當它沒有背景時直接呼叫的是dispatchDraw方法, 而繞過了onDraw方法,當它有背景的時候就呼叫onDraw方法,而onDraw()方法裡包含了dispatchDraw方法的呼叫。也就是說這裡必須複寫dispathDraw方法,否則將沒效果。

如何用10行程式碼讓app全域性置灰

定義好ViewGroup以後,接下來將它使用到頁面的根佈局上面,執行專案看結果:

如何用10行程式碼讓app全域性置灰

我們看到頁面確實也成功灰度化了,心中暗暗竊喜,終於可以減少很多工作量了。然而問題還是有的,專案中有幾百個頁面,每個頁面ViewGroup又不一樣,我難道要先準備GrayLinearLayout、GrayRelativeLayout、GrayFramelayout等等,然後一個一個去換根佈局嗎,想想都有點累,那有沒有更加優雅的方式呢?

三、步步逼近,從原始碼中尋找最優雅的全域性替換方式

一般來說,最好用的方案往往來源於原始碼中,這也是資深程式設計師經常檢視原始碼的原因。帶著這個疑問我們先來簡單複習一下View的載入過程,在我之前分享外掛化的時候有簡單提到過。我們從setContentView方法中一路點下去,最終會看到呼叫了LayoutInflater類中的createViewFromTag方法,這段呼叫過程很簡單,沒看過的同學可以親自去檢視一下。筆者點了很多次了,這裡就直接貼出來相關程式碼吧,重點是紅框裡面的部分。

如何用10行程式碼讓app全域性置灰

在紅框裡是具體的生成View過程,這裡分為3種情況,優先Factory2,其次是Factory,最後是預設的onCreateView方法。關於這裡為什麼會有3種情況,是因為歷史原因,為了相容AppCompact。預設情況下,前兩個都是為空的,會直接進入onCreateView方法。那麼思路來了,我們可以可以在載入View的時候,通過替換BaseActivity的佈局統一替換所有頁面呢?

方式一:通過setFactory方法給定我們自己的Factory從而替代系統的載入View,然後實現統一替換。

在Activity的onCreate方法中呼叫以下程式碼,hook住系統載入View的流程,根據前面的原始碼,如果設定了factory將進入自定義的onCreateView方法而不再進入系統的onCreateView方法。看過AMS或者事件分發流程的同學肯定知道,在我們自己寫的xml根佈局之上還有一個系統的FrameLayout,關於這一塊不太懂的同學可以去看我之前的事件分發文章,裡面有詳細講到。這裡我們就利用這一點,找到這個id是"content"的系統FrameLayout,然後將該FrameLayout替換成我們自己的帶有置灰程式碼的新FrameLayout即可。

如何用10行程式碼讓app全域性置灰

方式二:走系統的流程,在系統的回撥方法中替換

通過前面的原始碼得知,在不設定Factory的情況下,將呼叫到系統的onCreateView方法,所以我們也可以直接在Activity的onCreateView方法中加上以上程式碼,效果是一樣的。

如何用10行程式碼讓app全域性置灰

這兩種方式差別不大,都是可以用的,這裡建議使用第二種方式,儘量走系統自身的回撥。

如何用10行程式碼讓app全域性置灰

總結

本次我們從app全域性置灰怎麼來實現這一話題,漸漸深入,不斷探索更優雅的實現方式。將一個ImageView置灰的猜想,引申出了十幾行程式碼就置換了專案中所有的View,本質也是通過檢視原始碼的方式,一步一步地套出了方案。當然以後專案中肯定還會出現其他類似的場景,所以平時一定要多學習原始碼,早晚都會用到的,以防不備。退一萬步來講,就算用不到的話,檢視原始碼本身就是學習,可以提升我們自己的學習能力,而學習能力就是萬金油,到處都能用上。看完以後對內容有疑問或者有改進建議的同學,歡迎一起探討學習,共同進步!


相關文章