想修改 Android 字體,你需要先了解一下 Typeface!!!
序
在 Android 下使用自定義字體已經(jīng)是一個比較常見的需求了,最近也做了個比較深入的研究。
那么按照慣例我又要出個一篇有關(guān) Android 修改字體相關(guān)的文章,但是寫下來發(fā)現(xiàn)內(nèi)容還挺多的,所以我決定將它們拆分一下,分幾篇來詳細的講解(可能是五篇)。主要會是一些常用的替換字體的方案,***還會介紹一些全局替換的方案,當(dāng)然也會包含***的 『Fonts in XML』的方案。
本篇是本系列的第二篇,之前已經(jīng)發(fā)布的文章,有興趣可以先看看。
一、開篇
如果你想要操作字體,無論是使用 Android 系統(tǒng)自帶的字體,還是加載自己內(nèi)置的 .ttf(TureType) 或者 .otf(OpenType) 格式的字體文件,你都需要使用到 Typeface 這個類。
本文就單獨來分析 Typeface 的一些源碼細節(jié),本文在本系列中,可能相對枯燥一些,但是我覺得它又是不可或缺的一部分,所以單獨拿出一篇文章來細細說它。
二、加載一個 Typeface
Typeface 的細節(jié),要講內(nèi)容還是挺多的,切聽我細細道來。
2.1 通過 AssetManager 加載字體
一般我們會將需要的內(nèi)置字體文件,放在 assets 目錄下面,之后就可以通過 Typeface.createFromAsset() 方法,獲得一個 Typeface 對象。
例如,現(xiàn)在在項目的 assets/fonts 目錄下,放一個字體 .ttf 文件。
然后,我們就可以在需要的時候加載它,這也是一段比較常見的代碼。
繼續(xù)看看 createFromAsset() 的源碼。
代碼很簡單,邏輯也很清晰。
首先會有判斷 sFallbackFonts 不能為 null ,否則直接拋出異常,sFallbackFonts 不是重點,這個之后再講。
它依賴 sDynamicTypefaceCache 來保證線程的安全。并且會使用 createAssetUid() 來獲取到這個字體的唯一 key ,通過這個唯一 key ,從 sDynamicTypefaceCache 中獲取已經(jīng)被加載過的字體,如果沒有的話,再創(chuàng)建一個 FontFamily 的對象,通過 FontFamily.addFontFromAsset() 方法,將這個字體文件加入進去,***通過 createFromFamiliesWithDefault() 中,直接創(chuàng)建一個字體,最終存放到 sDynamicTypefaceCache 中去做一道緩存。
createFromFamiliesWithDefault() 方法需要傳遞一個 FontFamily 的數(shù)組,它本身也只是將這些 FontFamily 所代表的共性提取出來,最終調(diào)用 nativeCreateFromArray() 這個 native 的方法,所以效率上應(yīng)該不會有太大的問題。
這也說明,其實放在 assets 目錄下的字體,只要通過 Typeface 加載過之后,它本身就會有一道緩存,之后再取也只是從緩存中獲取,并不會影響性能。
而 sDynamicTypefaceCache 是一個基于 Lru 算法的,***存儲 16 個字體的一個緩存。
2.2 通過文件路徑加載字體
Typeface 除了可以從 assets 目錄下,加載字體文件,它還可以加載其它地方存儲的字體文件,并提供了方便的 Api。
最終也是通過字體文件的絕對路徑進行加載,這部分邏輯也很好理解。一樣是使用到了 FontFamily ,一樣是使用到了 createFromFamilyWithDefault()。
這些并沒有用到什么新的內(nèi)容,就不再展開細說一遍了。
2.3 通過字體名稱獲取字體
我們知道,Typeface 還可以管理一些 Android 系統(tǒng)自帶的字體,這些字體,如果想要獲取,也可以通過 Typeface 來加載,只需要傳遞進去對應(yīng)的名稱即可。
可以看到,它除了需要傳遞一個 familyName 之外,還需要傳遞一個 style ,這里的 style ,就是之前說的 android:textStyle 傳遞的值,用于設(shè)定字體的粗體(bold)、斜體(italic)等參數(shù)的。
這個方法,其實最終調(diào)用的是另外一個 create() 方法的重載,這個方法后面會詳細講解到。將它單拎出來講解,是因為它其中涉及到一個 sSystemFontMap 對象。
sSystemFontMap 是在 Typeface 的初始化方法 init() 中進行初始化的。
可以看到,它實際上是通過 getSystemFontConfigLocation() 中,讀取到本地支持的字體文件,然后將它們一次性加載進行,供后面直接使用。
秉承了 Linux 的傳統(tǒng),所有的配置都寫在文件里,這里也是直接從文件里讀取,getSystemFontConfigLocation() 方法獲取到的只是一個配置的路徑,最終讀取的是 FONTS_CONFIG 配置的 fonts.xml 文件。
2.4 通過 Typeface 獲得一個新的 Typeface
到這里,該講到前面提到的 create() 方法了,這里需要傳遞進來一個 Typeface 對象,并通過設(shè)置 style,為這個原始的 Typeface 字體類附加新的效果。
而這個過程也是不需要我們額外關(guān)心效率的問題的。它也提供了一個 sTypefaceCache 的緩存,來緩存我們曾經(jīng)使用的的系統(tǒng)默認字體。
三、Typeface 的其它細節(jié)
到這里基本上就已經(jīng)講解清楚 Typeface 的使用了,但是還有一些其它的細節(jié),可以單獨拎出來進行額外的講解。
3.1 Typeface 的初始化
Typeface 的初始化,是放在靜態(tài)代碼塊中的,它會初始化一些我們常用的系統(tǒng)默認字體,存儲起來方便我們使用。
這里會先調(diào)用 init() 方法,加載系統(tǒng)自帶的字體,然后再初始化一系列,例如 DEFAULT 、SNAS_SERIF 等自帶字體。
所以如果我們只是需要獲取一個系統(tǒng)自帶的字體,直接使用這里初始化的一些常量字體即可。
它還會將 DEFAULT 字體,默認初始化一個 sDefaults 的數(shù)組,在其中幫我們預(yù)加載好粗體、斜體等常用的 Style。
如果想要使用它,Typeface 也提供了對應(yīng)的方法。
3.2 Typeface 中的 Style
前面一直有提到一個 Style 的概念,它是可以通過 android:textStyle 屬性設(shè)置的,包括粗體、斜體等樣式。
在 Typeface 中,這些樣式也對應(yīng)了一個個的常量,并且 Typeface 也提供了對應(yīng)的 Api,讓我們獲取到當(dāng)前字體的樣式。
3.3 Typeface 中的 Native 方法
在 Typeface 中,所有最終操作到加載字體的部分,全部都是 native 的方法。而 native 方法就是以效率著稱的,這里只需要保證不頻繁的調(diào)用(Typeface 已經(jīng)做好了緩存,不會頻繁的調(diào)用),基本上也不會存在效率的問題。
3.4 簡單了解一下 FontFamily
FontFamily 在前面很多方法內(nèi)都用到了。它實際上就是去讀取字體文件的數(shù)據(jù)流,然后再通過 native 方法去加載字體。
拿 addFont() 方法舉例,它會先獲取 FileInputStream 對象,轉(zhuǎn)換成一個 ByteBuffer 然后傳遞給 native 方法 nAddFont() 來加載字體。
這個對象,了解一下就可以了,沒有什么太復(fù)雜的邏輯。
四、小結(jié)
到這里就已經(jīng)講解清楚 Typeface 的所有內(nèi)容,看完本篇文章心里也有底去使用 Typeface 了。
總結(jié)來說:
Typeface 提供了一系列的 createXxx()方法,用于從不同的地方加載字體。
Typeface 支持從系統(tǒng)默認字體、字體文件以及 assets 目錄下,加載字體。
Typeface 本身已經(jīng)支持字體緩存,我們只需要放心使用,不需要自身再額外緩存一遍。
Typeface 內(nèi)部最終調(diào)用的都是 native 方法,所以也不存在什么效率的問題。

【本文為51CTO專欄作者“張旸”的原創(chuàng)稿件,轉(zhuǎn)載請通過微信公眾號聯(lián)系作者獲取授權(quán)】