Android 修改字體,跳不過的 Typeface

在 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 文件。

/f-path.png

然后,我們就可以在需要的時(shí)候加載它,這也是一段比較常見的代碼。

/f-createfont.png

繼續(xù)看看 createFromAsset() 的源碼。

/f-createassets.png

代碼很簡(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è)緩存。

/f-sdyna.png

2.2 通過文件路徑加載字體

Typeface 除了可以從 assets 目錄下,加載字體文件,它還可以加載其它地方存儲(chǔ)的字體文件,并提供了方便的 Api。

/f-createfromfile.png

最終也是通過字體文件的絕對(duì)路徑進(jìn)行加載,這部分邏輯也很好理解。一樣是使用到了 FontFamily ,一樣是使用到了 createFromFamilyWithDefault()

這些并沒有用到什么新的內(nèi)容,就不再展開細(xì)說一遍了。

2.3 通過字體名稱獲取字體

我們知道,Typeface 還可以管理一些 Android 系統(tǒng)自帶的字體,這些字體,如果想要獲取,也可以通過 Typeface 來加載,只需要傳遞進(jìn)去對(duì)應(yīng)的名稱即可。

/f-createname.png

可以看到,它除了需要傳遞一個(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)行初始化的。

/f-init.jpg

可以看到,它實(shí)際上是通過 getSystemFontConfigLocation() 中,讀取到本地支持的字體文件,然后將它們一次性加載進(jìn)行,供后面直接使用。

/f-getSystemFont.png

秉承了 Linux 的傳統(tǒng),所有的配置都寫在文件里,這里也是直接從文件里讀取,getSystemFontConfigLocation() 方法獲取到的只是一個(gè)配置的路徑,最終讀取的是 FONTS_CONFIG 配置的 fonts.xml 文件。

2.4 通過 Typeface 獲得一個(gè)新的 Typeface

到這里,該講到前面提到的 create() 方法了,這里需要傳遞進(jìn)來一個(gè) Typeface 對(duì)象,并通過設(shè)置 style,為這個(gè)原始的 Typeface 字體類附加新的效果。

/f-createtypeface.png

而這個(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ǔ)起來方便我們使用。

/f-static.png

這里會(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)的方法。

/f-defaultStyle.png

3.2 Typeface 中的 Style

前面一直有提到一個(gè) Style 的概念,它是可以通過 android:textStyle 屬性設(shè)置的,包括粗體、斜體等樣式。

在 Typeface 中,這些樣式也對(duì)應(yīng)了一個(gè)個(gè)的常量,并且 Typeface 也提供了對(duì)應(yīng)的 Api,讓我們獲取到當(dāng)前字體的樣式。

/f-fontStyle.png

3.3 Typeface 中的 Native 方法

在 Typeface 中,所有最終操作到加載字體的部分,全部都是 native 的方法。而 native 方法就是以效率著稱的,這里只需要保證不頻繁的調(diào)用(Typeface 已經(jīng)做好了緩存,不會(huì)頻繁的調(diào)用),基本上也不會(huì)存在效率的問題。

/f-native.png

3.4 簡(jiǎn)單了解一下 FontFamily

FontFamily 在前面很多方法內(nèi)都用到了。它實(shí)際上就是去讀取字體文件的數(shù)據(jù)流,然后再通過 native 方法去加載字體。

/f-addfont.png

addFont() 方法舉例,它會(huì)先獲取 FileInputStream 對(duì)象,轉(zhuǎn)換成一個(gè) ByteBuffer 然后傳遞給 native 方法 nAddFont() 來加載字體。

這個(gè)對(duì)象,了解一下就可以了,沒有什么太復(fù)雜的邏輯。

四、小結(jié)

到這里就已經(jīng)講解清楚 Typeface 的所有內(nèi)容,看完本篇文章心里也有底去使用 Typeface 了。

總結(jié)來說:

  1. Typeface 提供了一系列的 createXxx() 方法用于從不同的地方加載字體。
  2. Typeface 支持從系統(tǒng)默認(rèn)字體、字體文件以及 assets 目錄下,加載字體。
  3. Typeface 本身已經(jīng)支持字體緩存,我們只需要放心使用,不需要自身再額外緩存一遍。
  4. 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》。

公眾號(hào)二維碼.jpg

點(diǎn)贊或者分享吧~

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 173,455評(píng)論 25 708
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 134,973評(píng)論 19 139
  • 1. Java基礎(chǔ)部分 基礎(chǔ)部分的順序:基本語法,類相關(guān)的語法,內(nèi)部類的語法,繼承相關(guān)的語法,異常的語法,線程的語...
    子非魚_t_閱讀 31,767評(píng)論 18 399
  • 版權(quán)聲明:本賬號(hào)發(fā)布文章均來自公眾號(hào),承香墨影(cxmyDev),版權(quán)歸承香墨影所有。每周會(huì)統(tǒng)一更新到這里,如果喜...
    承香墨影閱讀 2,996評(píng)論 0 10
  • E154 The Cuban national assembly announced on Friday that...
    一日一譯閱讀 198評(píng)論 0 1