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