1. 程式人生 > >怎樣“無痛”全域性替換字型

怎樣“無痛”全域性替換字型

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

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

期待你持續關注。

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

一、前言

上一篇講解了通過替換 AppCompatDelegate 來達到替換控制元件的目的,從而替換成我們需要的可設定自定義字型的控制元件,來達到替換字型的目的。

現在大多數人應該看出來了,到最後實現的目標就是如何快速、低入侵的替換全域性控制元件,然後對這些控制元件進行重寫,就可以達到我們很多的目的。換字型只是這其中的一種應用,還有其它的,例如:換膚、無痕埋點等等,都是有可借鑑的地方的。

本文再介紹一種方式,通過 LayoutInflaterCompat.setFactory()

 替換掉 LayoutInflaterFactory 或者 LayoutInflater.Factory2,來達到我們替換控制元件的目的,從而實現全域性字型的替換。

接下來開始介紹所有的技術細節。

二、setFactory()

2.1 setFactory() 的技術原理

對大家而言,LayoutInflater 應該是不陌生的,所有需要動態載入 layout-xml 中的 View 的地方都需要用到它的 inflater() 方法,例如:ListView、RecyclerView。

而本文需要用到的是它另外兩個 Api 方法,setFactory() 和 setFactory2()

。它們的方法簽名如下。

 

/setFactory.png/setFactory.png

 

這兩個方法分別接收 Factory 和 Factory2 ,它們兩個都是 Interface。並且這兩個方法的功能也是類似的。只是 setFactory2() 是在 Api Level 11 之後引入的,使用那個取決於專案的 minSdkVersion。

不過一般而言,我們也不需要直接使用它。我們需要只用 Support.v4 包中,為我們提供的 LayoutInflaterCompat 這個相容類來做處理。和所有的相容類一樣,它其中會有一個 IMPL的變數,會根據不同的 Api Level 初始化不同的例項。

 

/compatImpl.png/compatImpl.png

 

可以看到,這裡只對 Api Level 21 作為一個分界,去處理邏輯,其中會有不同的實現,這裡有興趣可以一探究竟,有時間會單獨出一篇文章來講解,這裡就不再深入了。

這裡,我們需要用到 LayoutInflaterCompat.setFactory() 方法,它實際上已經被標記為 @Deprecated 了,一般推薦我們使用 LayoutInflaterCompat.setFactory2(),但是它們的功能是一致的,這裡就不糾結這些細節了。

 

/impl-setfactory.png/impl-setfactory.png

 

可以看到,setFactory() 接收一個 LayoutInflaterFactory 的物件,它實際上是一個介面,需要我們實現其中的 onCreateView() 方法。

 

/LayoutFactory.png/LayoutFactory.png

 

我們這裡主要的功能,就在於實現 onCreateView() 方法,將我們需要的控制元件在這個方法中替換掉。

2.2 舉個例子

對著原始碼說太乾了。下面我舉個實際的例子,相信就可以說明問題了。

首先我新建一個 Activity,在 super.onCreate() 之前,通過 LayoutInflaterCompat 重新設定 Factory,在關鍵地方列印好 Log。

 

/setfactory-javacode.png/setfactory-javacode.png

 

再宣告一個佈局,讓它去顯示 layout-XML 佈局,層級很簡單,就是一個 LinearLayout 中間包含了一個 TextView。

然後,我們執行起來看看輸出的 Log ,這裡撇開了 DecorView 等這些佈局的列印,只看關鍵部分。

 

/setFactory-log.png/setFactory-log.png

 

從 Log 輸出可以看出,實際上,你所有佈局的控制元件,都會經過 LayoutInflaterFactory.onCreateView() 方法走一遍,去實現初始化的過程,在其中可以有效的分辨出是什麼控制元件,以及它有什麼屬性。

並且 onCreateView() 方法的返回值,就是一個 View,如果要替換該 View,可以在此處將其初始化後返回回去即可。

三、利用 LayoutInflater 替換字型

既然原理都清楚了,那麼我們接下來就開始實際操作一下,如何通過替換 LayoutInflaterFactory 來達到替換控制元件,從而達到替換字型的目的。

首先,定義一個 Activity 為基類,其中在 super..onCreate() 方法之前,呼叫 LayoutInflaterCompat.setFactory() ,然後將它的替換為 我們自己定義的 CustomFontCompatDelegate 類。

 

/demo-activitycode.png/demo-activitycode.png

 

CustomFontCompatDelegate 的實現,也非常的簡單,只需要在它的 onCreateView() 方法中,替換掉 TextView 就可以。

 

/demo-delegate-code.png/demo-delegate-code.png

 

其實,所有替換字型的邏輯,都在 FontTextView 中,接下來我們再看看 FontTextView 的邏輯。

 

/fontTextView.png/fontTextView.png

 

可以看到,在 FontTextView 中,直接完整的將字型替換成我們在 assets 目錄下存放的 custom_font.ttf 字型檔案。

到這裡就完成了基本的功能,我們接下來看看如何使用它。

只需要使用一個 Activity ,繼承我們剛才實現的 CustomFontActivity,然後寫一個簡單的佈局,其中有三個 TextView。

 

/demo-activityxml.png/demo-activityxml.png

 

最後,我們再來看看執行後的效果。

 

/f-fontimage.png/f-fontimage.png

 

四、小結

到這裡基本上就介紹清楚如何通過 LayoutInflaterCompat.setFactory() 去替換 Factory 這個介面,達到我們替換控制元件的目的,從而完美的替換全域性的字型。

但是實際開發過程中,依然需要考慮所有可以顯示文字的控制元件,例如:TextView、EditText、Button 等等,這些都是我們需要重寫的控制元件。