4

在 Android 下使用自定义字体已经是一个比较常见的需求了,最近也做了个比较深入的研究。

那么按照惯例我又要出个一篇有关 Android 修改字体相关的文章,但是写下来发现内容还挺多的,所以我决定将它们拆分一下,分几篇来详细的讲解。主要会是一些常用的替换字体的方案,最后还会介绍一些全局替换的方案,当然也会包含最新的 『Fonts in XML』的方案。

期待你持续关注。

本篇是本系列的第九篇,之前已经发布的文章,有兴趣可以先看看。

一、前言

之前已经介绍了很多种,快速、低入侵的替换全局字体的方式。但是大多数情况下,我们需要实现的功能,一定已经有现成的实现方案。

本文就介绍一个 Github 上,比较火的全局替换字体的开源库,差不多阅读文档加集成,一个小时全局替换字体不是梦。

这个开源替换字体库就是 Calligraphy:

https://github.com/chrisjenx/...

二、如何使用Calligraphy

既然是要接入开源库来全局替换字体,先来看看它可以实现的效果。

/screenshot.png

接下来,我们开始一步步集成它。

2.1 添加 Gradle 依赖

Calligraphy 支持 Gradle 和 jar 的接入方式,这里使用 Gradle 来接入。

/gradle-denpen.png

2.2 添加字体文档到项目内

Calligraphy 支持的文件,可以放在 assets/ 目录下,当然,我们可以再在其中建立一个文件夹来专门的存放字体文件。

/assets-font-file.png

2.3 初始化 Calligraphy

Calligraphy 使用 CalligraphyConfig 类,来进行初始化。它需要在 App 的入口,Application.onCreate() 中调用。

/init-method.png

初始化主要是为了指定一些默认的配置,例如:默认字体、默认属性值。

2.4 替换 Context

Calligraphy 对 Activity 的 Context,进行了一次包装,需要使用它包装的 Context,才可以达到替换字体的效果。所以还需要重写 BaseActivity 中的 attachBaseContext() 方法,将其替换成 Calligraphy 为我们提供的 Context 的包装类 CalligraphyContextWrapper。

/attach-base-context.png

2.5 使用 Calligraphy

到这里,就完成了 Calligraphy 的配置了,我们只需要在 TextView 中,通过属性去使用它就好了,它配置的是我们字体文件,在 assets 目录下的路径。

/text-layout-xml.png

2.6 查缺补漏

Calligraphy 使用起来还是很方便的,并且也支持更多的配置方式,例如: Style、Theme 都可以。

具体的使用细节,大家还是阅读文档了解更方便。

三、Calligraphy的原理

我们使用一个开源库,当然要理解它的原理才能放心使用在商业项目上,接下来,我们就来分析一下 Calligraphy 的实现原理,看看和之前介绍的方式,有没有什么区别。

先来看看 Calligraphy 的整体结构。

/call-path.png

可以看到,它一共需要的类非常的少,算是一个比较精简的库了,并且它并没有重写 TextView ,所以应该是通过其它的方式来做到字体的替换的。

我们先来看看在 Application 需要调用的配置类, CalligraphyConfig 的源码。

/config-methon.png

CalligraphyConfig 使用 Builder 的模式去初始化自己,可以看到这里只是设置了一些配置项,并没有实际的业务逻辑。

/config-get.png

CalligraphyConfig 初始化之后,就以静态变量存储起来,供其它地方使用,是一种单例的模式,但是并没有考虑线程安全的问题。

既然 CalligraphyConfig 没有实际的逻辑,那么接下去应该如何追踪重要的代码呢?

仔细观察之前配置项里,需要重写 Activity.attachBaseContext() 方法,这里会传递它重写的一个 Context 的包装类 CalligraphyContextWrapper,所以接下来我们再看看 CalligraphyContextWrapper 的源码逻辑。

/getSystemServer.png

读了 CalligraphyContextWrapper 源码之后,你会发现它最重要的就是重写了 getSystemService() 方法,当它是 LAYOUT_INFLATER_SERVICE 的时候,将自己的 CalligraphyLayoutInflater 类,返回回去。

那么,这里的 LAYOUT_INFLATER_SERVICE 到底是什么呢?

我想大家应该对 LayoutInflater 不陌生,从 layout-xml 加载 View 的时候,都需要用到它,相信下面这段代码,应该大家都不陌生。

/layoutinfalter-code.png

再仔细看看 LayoutInflater.from() 方法的源码。

/layoutinflalter-from.png

可以看到,这里获得 LayoutInflater 对象的时候,用到的就是 LAYOUT_INFLATER_SERVICE。

所以 CalligraphyContextWrapper.getSystemService() 方法被重写的目的,就是为了替换掉 LayoutInflater 对象,所以可以猜想,设置自定义字体的地方,就在自定义的 LayoutInflater 中。

继续查看 CalligraphyLayoutInflater 的源码,最终修改字体的逻辑,是在 CalligraphyContextWrappe 的 onViewCreatedInternal() 方法里面。

/view-create-interval.png

它会取出我们自定义属性上设置的值,然后设置到初始化好的 TextView 上去。

四、Calligraphy 小结

到此就完成了 Calligraphy 的主要逻辑追踪,几个核心技术点:

  1. Calligraphy 不需要重写 TextView 之类的控件。
  2. Calligraphy 重写了 LayoutInflater 。
  3. Calligraphy 在 attachBaseContext() 方法中,替换掉 ContextWrapper。
  4. 又通过自定义的 ContextWrapper 的 getSystemService() 方法,将 LayoutInflater 替换成库里重写的 CalligraphyLayoutInflater。
  5. 在 CalligraphyLayoutInflater 中,拦截我们需要的 TextView 和其子类,对它们的字体替换成我们设置的字体。

当然,实际上,开源库之所以可以流传的比较广,它还做了更多的细节处理,但是我们一般分析开源库,只需要关心主线逻辑就可以了。

整体来说 Calligraphy 没有什么大毛病,可以放心使用,当然如果你用了一些同样依赖此原理的第三方库,可能会有冲突,这个就只能具体问题具体分析了。

今天在承香墨影公众号的后台,回复『成长』。我会送你一些我整理的学习资料,包含:Android反编译、算法。Web项目源码。

推荐阅读:

点赞或者分享吧~


plokmju88
1.5k 声望132 粉丝

大家好,我是承香墨影。