序
在 Android 下使用自定义字体已经是一个比较常见的需求了,最近也做了个比较深入的研究。
那么按照惯例我又要出个一篇有关 Android 修改字体相关的文章,但是写下来发现内容还挺多的,所以我决定将它们拆分一下,分几篇来详细的讲解。主要会是一些常用的替换字体的方案,最后还会介绍一些全局替换的方案,当然也会包含最新的 『Fonts in XML』的方案。
本篇是本系列的第九篇,之前已经发布的文章,有兴趣可以先看看。
利用 AppCompatDelegate ,全局替换全局字体
一、前言
之前已经介绍了很多种,快速、低入侵的替换全局字体的方式。但是大多数情况下,我们需要实现的功能,一定已经有现成的实现方案。
本文就介绍一个 Github 上,比较火的全局替换字体的开源库,差不多阅读文档加集成,一个小时全局替换字体不是梦。
这个开源替换字体库就是 Calligraphy:
https://github.com/chrisjenx/Calligraphy
二、如何使用Calligraphy
既然是要接入开源库来全局替换字体,先来看看它可以实现的效果。
接下来,我们开始一步步集成它。
2.1 添加 Gradle 依赖
Calligraphy 支持 Gradle 和 jar 的接入方式,这里使用 Gradle 来接入。
2.2 添加字体文档到项目内
Calligraphy 支持的文件,可以放在 assets/ 目录下,当然,我们可以再在其中建立一个文件夹来专门的存放字体文件。
2.3 初始化 Calligraphy
Calligraphy 使用 CalligraphyConfig 类,来进行初始化。它需要在 App 的入口,Application.onCreate() 中调用。
初始化主要是为了指定一些默认的配置,例如:默认字体、默认属性值。
2.4 替换 Context
Calligraphy 对 Activity 的 Context,进行了一次包装,需要使用它包装的 Context,才可以达到替换字体的效果。所以还需要重写 BaseActivity 中的 attachBaseContext() 方法,将其替换成 Calligraphy 为我们提供的 Context 的包装类 CalligraphyContextWrapper。
2.5 使用 Calligraphy
到这里,就完成了 Calligraphy 的配置了,我们只需要在 TextView 中,通过属性去使用它就好了,它配置的是我们字体文件,在 assets 目录下的路径。
2.6 查缺补漏
Calligraphy 使用起来还是很方便的,并且也支持更多的配置方式,例如: Style、Theme 都可以。
具体的使用细节,大家还是阅读文档了解更方便。
三、Calligraphy的原理
我们使用一个开源库,当然要理解它的原理才能放心使用在商业项目上,接下来,我们就来分析一下 Calligraphy 的实现原理,看看和之前介绍的方式,有没有什么区别。
先来看看 Calligraphy 的整体结构。
可以看到,它一共需要的类非常的少,算是一个比较精简的库了,并且它并没有重写 TextView ,所以应该是通过其它的方式来做到字体的替换的。
我们先来看看在 Application 需要调用的配置类, CalligraphyConfig 的源码。
CalligraphyConfig 使用 Builder 的模式去初始化自己,可以看到这里只是设置了一些配置项,并没有实际的业务逻辑。
CalligraphyConfig 初始化之后,就以静态变量存储起来,供其它地方使用,是一种单例的模式,但是并没有考虑线程安全的问题。
既然 CalligraphyConfig 没有实际的逻辑,那么接下去应该如何追踪重要的代码呢?
仔细观察之前配置项里,需要重写 Activity.attachBaseContext() 方法,这里会传递它重写的一个 Context 的包装类 CalligraphyContextWrapper,所以接下来我们再看看 CalligraphyContextWrapper 的源码逻辑。
读了 CalligraphyContextWrapper 源码之后,你会发现它最重要的就是重写了 getSystemService() 方法,当它是 LAYOUT_INFLATER_SERVICE 的时候,将自己的 CalligraphyLayoutInflater 类,返回回去。
那么,这里的 LAYOUT_INFLATER_SERVICE 到底是什么呢?
我想大家应该对 LayoutInflater 不陌生,从 layout-xml 加载 View 的时候,都需要用到它,相信下面这段代码,应该大家都不陌生。
再仔细看看 LayoutInflater.from() 方法的源码。
可以看到,这里获得 LayoutInflater 对象的时候,用到的就是 LAYOUT_INFLATER_SERVICE。
所以 CalligraphyContextWrapper.getSystemService() 方法被重写的目的,就是为了替换掉 LayoutInflater 对象,所以可以猜想,设置自定义字体的地方,就在自定义的 LayoutInflater 中。
继续查看 CalligraphyLayoutInflater 的源码,最终修改字体的逻辑,是在 CalligraphyContextWrappe 的 onViewCreatedInternal() 方法里面。
它会取出我们自定义属性上设置的值,然后设置到初始化好的 TextView 上去。
四、Calligraphy 小结
到此就完成了 Calligraphy 的主要逻辑追踪,几个核心技术点:
Calligraphy 不需要重写 TextView 之类的控件。
Calligraphy 重写了 LayoutInflater 。
Calligraphy 在 attachBaseContext() 方法中,替换掉 ContextWrapper。
又通过自定义的 ContextWrapper 的 getSystemService() 方法,将 LayoutInflater 替换成库里重写的 CalligraphyLayoutInflater。
在 CalligraphyLayoutInflater 中,拦截我们需要的 TextView 和其子类,对它们的字体替换成我们设置的字体。
当然,实际上,开源库之所以可以流传的比较广,它还做了更多的细节处理,但是我们一般分析开源库,只需要关心主线逻辑就可以了。
整体来说 Calligraphy 没有什么大毛病,可以放心使用,当然如果你用了一些同样依赖此原理的第三方库,可能会有冲突,这个就只能具体问题具体分析了。
【本文为51CTO专栏作者“张旸”的原创稿件,转载请通过微信公众号联系作者获取授权】