序
在 Android 下使用自定義字型已經是一個比較常見的需求了,最近也做了個比較深入的研究。
那麼按照慣例我又要出個一篇有關 Android 修改字型相關的文章,但是寫下來發現內容還挺多的,所以我決定將它們拆分一下,分幾篇來詳細的講解。主要會是一些常用的替換字型的方案,最後還會介紹一些全域性替換的方案,當然也會包含最新的 『Fonts in XML』的方案。
期待你持續關注。
本篇是本系列的第九篇,之前已經發布的文章,有興趣可以先看看。
- Android 字型修改概述|開篇
- 修改字型需要了解 Typeface 的所有細節
- 簡單粗暴的方式,修改字型
- 利用反射,修改全域性字型
- 利用 AppCompatDelegate ,全域性替換全域性字型
- 通過修改 LayoutInflater,全域性替換字型!
- 自定義屬性,支援多個字型檔案!
- Android 可下載字型,Font in xml!
一、前言
之前已經介紹了很多種,快速、低入侵的替換全域性字型的方式。但是大多數情況下,我們需要實現的功能,一定已經有現成的實現方案。
本文就介紹一個 Github 上,比較火的全域性替換字型的開源庫,差不多閱讀文件加整合,一個小時全域性替換字型不是夢。
這個開源替換字型庫就是 Calligraphy:
二、如何使用Calligraphy
既然是要接入開源庫來全域性替換字型,先來看看它可以實現的效果。
接下來,我們開始一步步整合它。
2.1 新增 Gradle 依賴
Calligraphy 支援 Gradle 和 jar 的接入方式,這裡使用 Gradle 來接入。
2.2 新增字型文件到專案內
Calligraphy 支援的檔案,可以放在 assets/
目錄下,當然,我們可以再在其中建立一個資料夾來專門的存放字型檔案。
2.3 初始化 Calligraphy
Calligraphy 使用 CalligraphyConfig 類,來進行初始化。它需要在 App 的入口,Application.onCreate()
中呼叫。
初始化主要是為了指定一些預設的配置,例如:預設字型、預設屬性值。
2.4 替換 Context
Calligraphy 對 Activity 的 Context,進行了一次包裝,需要使用它包裝的 Context,才可以達到替換字型的效果。所以還需要重寫 BaseActivity 中的 attachBaseContext()
方法,將其替換成 Calligraphy 為我們提供的 Context 的包裝類 CalligraphyContextWrapper。
2.5 使用 Calligraphy
到這裡,就完成了 Calligraphy 的配置了,我們只需要在 TextView 中,通過屬性去使用它就好了,它配置的是我們字型檔案,在 assets 目錄下的路徑。
2.6 查缺補漏
Calligraphy 使用起來還是很方便的,並且也支援更多的配置方式,例如: Style、Theme 都可以。
具體的使用細節,大家還是閱讀文件瞭解更方便。
三、Calligraphy的原理
我們使用一個開源庫,當然要理解它的原理才能放心使用在商業專案上,接下來,我們就來分析一下 Calligraphy 的實現原理,看看和之前介紹的方式,有沒有什麼區別。
先來看看 Calligraphy 的整體結構。
可以看到,它一共需要的類非常的少,算是一個比較精簡的庫了,並且它並沒有重寫 TextView ,所以應該是通過其它的方式來做到字型的替換的。
我們先來看看在 Application 需要呼叫的配置類, CalligraphyConfig 的原始碼。
CalligraphyConfig 使用 Builder 的模式去初始化自己,可以看到這裡只是設定了一些配置項,並沒有實際的業務邏輯。
CalligraphyConfig 初始化之後,就以靜態變數儲存起來,供其它地方使用,是一種單例的模式,但是並沒有考慮執行緒安全的問題。
既然 CalligraphyConfig 沒有實際的邏輯,那麼接下去應該如何追蹤重要的程式碼呢?
仔細觀察之前配置項裡,需要重寫 Activity.attachBaseContext()
方法,這裡會傳遞它重寫的一個 Context 的包裝類 CalligraphyContextWrapper,所以接下來我們再看看 CalligraphyContextWrapper 的原始碼邏輯。
讀了 CalligraphyContextWrapper 原始碼之後,你會發現它最重要的就是重寫了 getSystemService()
方法,當它是 LAYOUT_INFLATER_SERVICE 的時候,將自己的 CalligraphyLayoutInflater 類,返回回去。
那麼,這裡的 LAYOUT_INFLATER_SERVICE 到底是什麼呢?
我想大家應該對 LayoutInflater 不陌生,從 layout-xml 載入 View 的時候,都需要用到它,相信下面這段程式碼,應該大家都不陌生。
再仔細看看 LayoutInflater.from()
方法的原始碼。
可以看到,這裡獲得 LayoutInflater 物件的時候,用到的就是 LAYOUT_INFLATER_SERVICE。
所以 CalligraphyContextWrapper.getSystemService() 方法被重寫的目的,就是為了替換掉 LayoutInflater 物件,所以可以猜想,設定自定義字型的地方,就在自定義的 LayoutInflater 中。
繼續檢視 CalligraphyLayoutInflater 的原始碼,最終修改字型的邏輯,是在 CalligraphyContextWrappe 的 onViewCreatedInternal()
方法裡面。
它會取出我們自定義屬性上設定的值,然後設定到初始化好的 TextView 上去。
四、Calligraphy 小結
到此就完成了 Calligraphy 的主要邏輯追蹤,幾個核心技術點:
- Calligraphy 不需要重寫 TextView 之類的控制元件。
- Calligraphy 重寫了 LayoutInflater 。
- Calligraphy 在
attachBaseContext()
方法中,替換掉 ContextWrapper。 - 又通過自定義的 ContextWrapper 的
getSystemService()
方法,將 LayoutInflater 替換成庫裡重寫的 CalligraphyLayoutInflater。 - 在 CalligraphyLayoutInflater 中,攔截我們需要的 TextView 和其子類,對它們的字型替換成我們設定的字型。
當然,實際上,開源庫之所以可以流傳的比較廣,它還做了更多的細節處理,但是我們一般分析開源庫,只需要關心主線邏輯就可以了。
整體來說 Calligraphy 沒有什麼大毛病,可以放心使用,當然如果你用了一些同樣依賴此原理的第三方庫,可能會有衝突,這個就只能具體問題具體分析了。
今天在承香墨影公眾號的後臺,回覆『成長』。我會送你一些我整理的學習資料,包含:Android反編譯、演算法。Web專案原始碼。
推薦閱讀:
點贊或者分享吧~