全域性修改預設字型,通過反射也能做到

承香墨影發表於2017-09-28

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

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

期待你持續關注。

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

一、前言

本文依然屬於 Android 修改字型的系列,本系列開始會介紹一些比較方便的全域性修改的方案,越往後的方案可能會越好一些,但是不一定最適用你現在的專案。

今天介紹的就是其中的一個,使用反射的方式,修改 Typeface 中的某個字型,來達到全域性替換的目的。

二、替換預設字型的思路

本文的很多預備的知識點,應該在之前的文章中就已經說清楚了,有興趣可以去看看完整的文章,《》。

這裡為了保證邏輯完整,還是大概說一下思路。

2.1 修改Typeface 的某個預設字型

首先需要明確一點,在 Android 中,所有操作字型的動作,都會使用到 Typeface 這個類。而系統預設的一些字型,也會在 Typeface 被載入的時候進行初始化,因為這些步驟在它的靜態程式碼塊內完成。

/f-init.png
/f-init.png

而這些字型都會定義成了 static final 的,所以一般我們是不能去修改它們的。

/f-staticfinal.png
/f-staticfinal.png

但是我們是可以使用反射的方式去修改被標記為 static final 的常量的,這個後面再將細節。

也就是說,我們只需要在初始化的實際,替換掉某個預設的字型,然後在Theme 內將預設字型字型標記為該字型,就可以達到替換的目的。

2.2 在 TextView 中預設的字型

在 TextView 的構造方法裡,設定字型的方法是 setTypefaceFromAttrs() ,下面是該方法的方法簽名。

/f-setAttr.png
/f-setAttr.png

在該方法的引數中,如果 familyName 為 null 的時候,會根據傳入的 typefaceIndex 去設定對應的字型,傳入到 setTypeface() 方法中。

再來看看 TextView 的構造方法中,獲取這幾個引數的地方。在預設什麼都不設定的情況下, familyName 就是為 null,而 typefaceIndex 為 -1。這兩個引數會先從 TextAppearance 中讀取屬性,再從 TextView 本身設定的 xml 屬性中讀取,後者會覆蓋前者。

可是 typefaceIndex 還會有一些其它的操作,例如 inputType 為 password 的時候,就會強行修改為 MONOSPACE。

/f-getAttr.png
/f-getAttr.png

最終,將處理後的結構,傳遞給 setTypefaceFromAttrs() 方法。

通過這些細節,我們就可以瞭解到,是在有一些情況下,是可以保證 TextView 使用的是我們的某個被載入到 Typeface 中的預設字型的。

條件就是:

fontFamily == null && typefaceIndex != -1

2.3 在 Theme 中,修改字型為預設字型樣式

對於一些預設的字型樣式,是可以直接在 Theme 中進行設定的,它的優先順序低於在頁面佈局的 xml 中,為 TextView 設定的字型樣式,但是如果不設定,那麼在 Theme 中的設定將會生效。

這個沒什麼好說的,我這裡用的主題就是 AppTheme,所以我在它裡面修改 android:typeface 就可以了。

/f-theme.png
/f-theme.png

三、通過反射修改字型

到這裡,基本的概念就已經講解清楚了,那麼我們就開始實際編寫程式碼來替換字型了。

3.1 修改 Theme

在 App 的主題中,修改 android:typeface 為 serif。

/f-theme.png
/f-theme.png

注意,這裡隨便選了一個預設字型,實際上使用 monospace 也是可以的,只需要和後面我們替換的字型保持一致即可。

當然這裡不推薦使用 monospace ,因為 TextView 本身還有一些邏輯會將 typefaceIndex 設定成 monospace,所以我們不要替換它比較好。

3.2 通過反射修改 Typeface 的字型

在 Typeface 中,是有一些被標記為 static final 的預設字型,因為上一步的 Theme 中,就是設定的 serif ,所以我們這裡替換它就好了。

完整的方法非常的簡單,就是通過反射拿到 Typeface.SERIF,然後使用反射將它修改成我們需要的字型即可。

/f-replace.png
/f-replace.png

因為這裡修改了 static final 的值,所以需要額外呼叫 setAccessible() 方法,它會修改 AccessibleObject 中的 overide 為 true,這個標記的意思,就是關閉對這個欄位改寫的安全檢查,從而讓我們可以替換 static final 的欄位。

3.3 在入口的地方,呼叫替換的方法

接下來就清晰了,我們只需要在 App 啟動的時候,呼叫一下 changeDefaultFont() 方法。

這裡直接在 Application.onCreate() 方法中,呼叫即可。

/f-myapp.png
/f-myapp.png

3.4 驗證執行結果

這個沒什麼了,直接寫個 Demo,正常使用 TextView 就可以了。

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

公眾號二維碼.jpg
公眾號二維碼.jpg

相關文章