Android 修改字体,跳不过的 Typeface

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

/f-path.png

然后,我们就可以在需要的时候加载它,这也是一段比较常见的代码。

/f-createfont.png

继续看看 createFromAsset() 的源码。

/f-createassets.png

代码很简单,逻辑也很清晰。

首先会有判断 sFallbackFonts 不能为 null ,否则直接抛出异常,sFallbackFonts 不是重点,这个之后再讲。

它依赖 sDynamicTypefaceCache 来保证线程的安全。并且会使用 createAssetUid() 来获取到这个字体的唯一 key ,通过这个唯一 key ,从 sDynamicTypefaceCache 中获取已经被加载过的字体,如果没有的话,再创建一个 FontFamily 的对象,通过 FontFamily.addFontFromAsset() 方法,将这个字体文件加入进去,最后通过 createFromFamiliesWithDefault() 中,直接创建一个字体,最终存放到 sDynamicTypefaceCache 中去做一道缓存。

createFromFamiliesWithDefault() 方法需要传递一个 FontFamily 的数组,它本身也只是将这些 FontFamily 所代表的共性提取出来,最终调用 nativeCreateFromArray() 这个 native 的方法,所以效率上应该不会有太大的问题。

这也说明,其实放在 assets 目录下的字体,只要通过 Typeface 加载过之后,它本身就会有一道缓存,之后再取也只是从缓存中获取,并不会影响性能。

而 sDynamicTypefaceCache 是一个基于 Lru 算法的,最大存储 16 个字体的一个缓存。

/f-sdyna.png

2.2 通过文件路径加载字体

Typeface 除了可以从 assets 目录下,加载字体文件,它还可以加载其它地方存储的字体文件,并提供了方便的 Api。

/f-createfromfile.png

最终也是通过字体文件的绝对路径进行加载,这部分逻辑也很好理解。一样是使用到了 FontFamily ,一样是使用到了 createFromFamilyWithDefault()

这些并没有用到什么新的内容,就不再展开细说一遍了。

2.3 通过字体名称获取字体

我们知道,Typeface 还可以管理一些 Android 系统自带的字体,这些字体,如果想要获取,也可以通过 Typeface 来加载,只需要传递进去对应的名称即可。

/f-createname.png

可以看到,它除了需要传递一个 familyName 之外,还需要传递一个 style ,这里的 style ,就是之前说的 android:textStyle 传递的值,用于设定字体的粗体(bold)、斜体(italic)等参数的。

这个方法,其实最终调用的是另外一个 create() 方法的重载,这个方法后面会详细讲解到。将它单拎出来讲解,是因为它其中涉及到一个 sSystemFontMap 对象。

sSystemFontMap 是在 Typeface 的初始化方法 init() 中进行初始化的。

/f-init.jpg

可以看到,它实际上是通过 getSystemFontConfigLocation() 中,读取到本地支持的字体文件,然后将它们一次性加载进行,供后面直接使用。

/f-getSystemFont.png

秉承了 Linux 的传统,所有的配置都写在文件里,这里也是直接从文件里读取,getSystemFontConfigLocation() 方法获取到的只是一个配置的路径,最终读取的是 FONTS_CONFIG 配置的 fonts.xml 文件。

2.4 通过 Typeface 获得一个新的 Typeface

到这里,该讲到前面提到的 create() 方法了,这里需要传递进来一个 Typeface 对象,并通过设置 style,为这个原始的 Typeface 字体类附加新的效果。

/f-createtypeface.png

而这个过程也是不需要我们额外关心效率的问题的。它也提供了一个 sTypefaceCache 的缓存,来缓存我们曾经使用的的系统默认字体。

三、Typeface 的其它细节

到这里基本上就已经讲解清楚 Typeface 的使用了,但是还有一些其它的细节,可以单独拎出来进行额外的讲解。

3.1 Typeface 的初始化

Typeface 的初始化,是放在静态代码块中的,它会初始化一些我们常用的系统默认字体,存储起来方便我们使用。

/f-static.png

这里会先调用 init() 方法,加载系统自带的字体,然后再初始化一系列,例如 DEFAULT 、SNAS_SERIF 等自带字体。

所以如果我们只是需要获取一个系统自带的字体,直接使用这里初始化的一些常量字体即可。

它还会将 DEFAULT 字体,默认初始化一个 sDefaults 的数组,在其中帮我们预加载好粗体、斜体等常用的 Style。

如果想要使用它,Typeface 也提供了对应的方法。

/f-defaultStyle.png

3.2 Typeface 中的 Style

前面一直有提到一个 Style 的概念,它是可以通过 android:textStyle 属性设置的,包括粗体、斜体等样式。

在 Typeface 中,这些样式也对应了一个个的常量,并且 Typeface 也提供了对应的 Api,让我们获取到当前字体的样式。

/f-fontStyle.png

3.3 Typeface 中的 Native 方法

在 Typeface 中,所有最终操作到加载字体的部分,全部都是 native 的方法。而 native 方法就是以效率著称的,这里只需要保证不频繁的调用(Typeface 已经做好了缓存,不会频繁的调用),基本上也不会存在效率的问题。

/f-native.png

3.4 简单了解一下 FontFamily

FontFamily 在前面很多方法内都用到了。它实际上就是去读取字体文件的数据流,然后再通过 native 方法去加载字体。

/f-addfont.png

addFont() 方法举例,它会先获取 FileInputStream 对象,转换成一个 ByteBuffer 然后传递给 native 方法 nAddFont() 来加载字体。

这个对象,了解一下就可以了,没有什么太复杂的逻辑。

四、小结

到这里就已经讲解清楚 Typeface 的所有内容,看完本篇文章心里也有底去使用 Typeface 了。

总结来说:

  1. Typeface 提供了一系列的 createXxx() 方法用于从不同的地方加载字体。
  2. Typeface 支持从系统默认字体、字体文件以及 assets 目录下,加载字体。
  3. Typeface 本身已经支持字体缓存,我们只需要放心使用,不需要自身再额外缓存一遍。
  4. Typeface 内部最终调用的都是 native 方法,所以也不存在什么效率的问题。

下篇预告

下期会介绍一些比较粗暴的替换全局字体的方案。有在新项目上的,也有在现有的成熟项目上的。期待你的持续关注。

另外,最近有一个关于跳槽的分享,我这边独家有一些优惠活动。如果你有兴趣,可以去看看《看我如何拿到上亿用户 App 家的 offer》。

公众号二维码.jpg

点赞或者分享吧~

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,362评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,330评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,247评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,560评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,580评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,569评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,929评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,587评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,840评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,596评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,678评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,366评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,945评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,929评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,165评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,271评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,403评论 2 342

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,393评论 25 707
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,585评论 18 139
  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 31,560评论 18 399
  • 版权声明:本账号发布文章均来自公众号,承香墨影(cxmyDev),版权归承香墨影所有。每周会统一更新到这里,如果喜...
    承香墨影阅读 2,690评论 0 10
  • E154 The Cuban national assembly announced on Friday that...
    一日一译阅读 190评论 0 1