全域性替換字型,開源庫更方便!!!

承香墨影發表於2017-10-18

在 Android 下使用自定義字型已經是一個比較常見的需求了,最近也做了個比較深入的研究。

那麼按照慣例我又要出個一篇有關 Android 修改字型相關的文章,但是寫下來發現內容還挺多的,所以我決定將它們拆分一下,分幾篇來詳細的講解。主要會是一些常用的替換字型的方案,最後還會介紹一些全域性替換的方案,當然也會包含最新的 『Fonts in XML』的方案。

期待你持續關注。

本篇是本系列的第九篇,之前已經發布的文章,有興趣可以先看看。

一、前言

之前已經介紹了很多種,快速、低入侵的替換全域性字型的方式。但是大多數情況下,我們需要實現的功能,一定已經有現成的實現方案。

本文就介紹一個 Github 上,比較火的全域性替換字型的開源庫,差不多閱讀文件加整合,一個小時全域性替換字型不是夢。

這個開源替換字型庫就是 Calligraphy:

github.com/chrisjenx/C…

二、如何使用Calligraphy

既然是要接入開源庫來全域性替換字型,先來看看它可以實現的效果。

/screenshot.png
/screenshot.png

接下來,我們開始一步步整合它。

2.1 新增 Gradle 依賴

Calligraphy 支援 Gradle 和 jar 的接入方式,這裡使用 Gradle 來接入。

/gradle-denpen.png
/gradle-denpen.png

2.2 新增字型文件到專案內

Calligraphy 支援的檔案,可以放在 assets/ 目錄下,當然,我們可以再在其中建立一個資料夾來專門的存放字型檔案。

/assets-font-file.png
/assets-font-file.png

2.3 初始化 Calligraphy

Calligraphy 使用 CalligraphyConfig 類,來進行初始化。它需要在 App 的入口,Application.onCreate() 中呼叫。

/init-method.png
/init-method.png

初始化主要是為了指定一些預設的配置,例如:預設字型、預設屬性值。

2.4 替換 Context

Calligraphy 對 Activity 的 Context,進行了一次包裝,需要使用它包裝的 Context,才可以達到替換字型的效果。所以還需要重寫 BaseActivity 中的 attachBaseContext() 方法,將其替換成 Calligraphy 為我們提供的 Context 的包裝類 CalligraphyContextWrapper。

/attach-base-context.png
/attach-base-context.png

2.5 使用 Calligraphy

到這裡,就完成了 Calligraphy 的配置了,我們只需要在 TextView 中,通過屬性去使用它就好了,它配置的是我們字型檔案,在 assets 目錄下的路徑。

/text-layout-xml.png
/text-layout-xml.png

2.6 查缺補漏

Calligraphy 使用起來還是很方便的,並且也支援更多的配置方式,例如: Style、Theme 都可以。

具體的使用細節,大家還是閱讀文件瞭解更方便。

三、Calligraphy的原理

我們使用一個開源庫,當然要理解它的原理才能放心使用在商業專案上,接下來,我們就來分析一下 Calligraphy 的實現原理,看看和之前介紹的方式,有沒有什麼區別。

先來看看 Calligraphy 的整體結構。

/call-path.png
/call-path.png

可以看到,它一共需要的類非常的少,算是一個比較精簡的庫了,並且它並沒有重寫 TextView ,所以應該是通過其它的方式來做到字型的替換的。

我們先來看看在 Application 需要呼叫的配置類, CalligraphyConfig 的原始碼。

/config-methon.png
/config-methon.png

CalligraphyConfig 使用 Builder 的模式去初始化自己,可以看到這裡只是設定了一些配置項,並沒有實際的業務邏輯。

/config-get.png
/config-get.png

CalligraphyConfig 初始化之後,就以靜態變數儲存起來,供其它地方使用,是一種單例的模式,但是並沒有考慮執行緒安全的問題。

既然 CalligraphyConfig 沒有實際的邏輯,那麼接下去應該如何追蹤重要的程式碼呢?

仔細觀察之前配置項裡,需要重寫 Activity.attachBaseContext() 方法,這裡會傳遞它重寫的一個 Context 的包裝類 CalligraphyContextWrapper,所以接下來我們再看看 CalligraphyContextWrapper 的原始碼邏輯。

/getSystemServer.png
/getSystemServer.png

讀了 CalligraphyContextWrapper 原始碼之後,你會發現它最重要的就是重寫了 getSystemService() 方法,當它是 LAYOUT_INFLATER_SERVICE 的時候,將自己的 CalligraphyLayoutInflater 類,返回回去。

那麼,這裡的 LAYOUT_INFLATER_SERVICE 到底是什麼呢?

我想大家應該對 LayoutInflater 不陌生,從 layout-xml 載入 View 的時候,都需要用到它,相信下面這段程式碼,應該大家都不陌生。

/layoutinfalter-code.png
/layoutinfalter-code.png

再仔細看看 LayoutInflater.from() 方法的原始碼。

/layoutinflalter-from.png
/layoutinflalter-from.png

可以看到,這裡獲得 LayoutInflater 物件的時候,用到的就是 LAYOUT_INFLATER_SERVICE。

所以 CalligraphyContextWrapper.getSystemService() 方法被重寫的目的,就是為了替換掉 LayoutInflater 物件,所以可以猜想,設定自定義字型的地方,就在自定義的 LayoutInflater 中。

繼續檢視 CalligraphyLayoutInflater 的原始碼,最終修改字型的邏輯,是在 CalligraphyContextWrappe 的 onViewCreatedInternal() 方法裡面。

/view-create-interval.png
/view-create-interval.png

它會取出我們自定義屬性上設定的值,然後設定到初始化好的 TextView 上去。

四、Calligraphy 小結

到此就完成了 Calligraphy 的主要邏輯追蹤,幾個核心技術點:

  1. Calligraphy 不需要重寫 TextView 之類的控制元件。
  2. Calligraphy 重寫了 LayoutInflater 。
  3. Calligraphy 在 attachBaseContext() 方法中,替換掉 ContextWrapper。
  4. 又通過自定義的 ContextWrapper 的 getSystemService() 方法,將 LayoutInflater 替換成庫裡重寫的 CalligraphyLayoutInflater。
  5. 在 CalligraphyLayoutInflater 中,攔截我們需要的 TextView 和其子類,對它們的字型替換成我們設定的字型。

當然,實際上,開源庫之所以可以流傳的比較廣,它還做了更多的細節處理,但是我們一般分析開源庫,只需要關心主線邏輯就可以了。

整體來說 Calligraphy 沒有什麼大毛病,可以放心使用,當然如果你用了一些同樣依賴此原理的第三方庫,可能會有衝突,這個就只能具體問題具體分析了。

今天在承香墨影公眾號的後臺,回覆『成長』。我會送你一些我整理的學習資料,包含:Android反編譯、演算法。Web專案原始碼。

推薦閱讀:

點贊或者分享吧~

相關文章