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