通过修改 LayoutInflater,全局替换字体!!!

Android全局字体替换
本文介绍了一种通过LayoutInflaterCompat.setFactory()方法替换LayoutInflaterFactory接口来实现全局字体替换的技术方案。通过重写onCreateView()方法,可以针对特定的控件如TextView进行自定义字体的设置。

/WechatIMG13.jpeg

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

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

期待你持续关注。

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

一、前言

上一篇讲解了通过替换 AppCompatDelegate 来达到替换控件的目的,从而替换成我们需要的可设置自定义字体的控件,来达到替换字体的目的。

现在大多数人应该看出来了,到最后实现的目标就是如何快速、低入侵的替换全局控件,然后对这些控件进行重写,就可以达到我们很多的目的。换字体只是这其中的一种应用,还有其它的,例如:换肤、无痕埋点等等,都是有可借鉴的地方的。

本文再介绍一种方式,通过 LayoutInflaterCompat.setFactory() 替换掉 LayoutInflaterFactory 或者 LayoutInflater.Factory2,来达到我们替换控件的目的,从而实现全局字体的替换。

接下来开始介绍所有的技术细节。

二、setFactory()

2.1 setFactory() 的技术原理

对大家而言,LayoutInflater 应该是不陌生的,所有需要动态加载 layout-xml 中的 View 的地方都需要用到它的 inflater() 方法,例如:ListView、RecyclerView。

而本文需要用到的是它另外两个 Api 方法,setFactory()setFactory2()。它们的方法签名如下。

/setFactory.png

这两个方法分别接收 Factory 和 Factory2 ,它们两个都是 Interface。并且这两个方法的功能也是类似的。只是 setFactory2() 是在 Api Level 11 之后引入的,使用那个取决于项目的 minSdkVersion。

不过一般而言,我们也不需要直接使用它。我们需要只用 Support.v4 包中,为我们提供的 LayoutInflaterCompat 这个兼容类来做处理。和所有的兼容类一样,它其中会有一个 IMPL 的变量,会根据不同的 Api Level 初始化不同的实例。

/compatImpl.png

可以看到,这里只对 Api Level 21 作为一个分界,去处理逻辑,其中会有不同的实现,这里有兴趣可以一探究竟,有时间会单独出一篇文章来讲解,这里就不再深入了。

这里,我们需要用到 LayoutInflaterCompat.setFactory() 方法,它实际上已经被标记为 @Deprecated 了,一般推荐我们使用 LayoutInflaterCompat.setFactory2(),但是它们的功能是一致的,这里就不纠结这些细节了。

/impl-setfactory.png

可以看到,setFactory() 接收一个 LayoutInflaterFactory 的对象,它实际上是一个接口,需要我们实现其中的 onCreateView() 方法。

/LayoutFactory.png

我们这里主要的功能,就在于实现 onCreateView() 方法,将我们需要的控件在这个方法中替换掉。

2.2 举个例子

对着源码说太干了。下面我举个实际的例子,相信就可以说明问题了。

首先我新建一个 Activity,在 super.onCreate() 之前,通过 LayoutInflaterCompat 重新设置 Factory,在关键地方打印好 Log。

/setfactory-javacode.png

再声明一个布局,让它去显示 layout-XML 布局,层级很简单,就是一个 LinearLayout 中间包含了一个 TextView。

然后,我们运行起来看看输出的 Log ,这里撇开了 DecorView 等这些布局的打印,只看关键部分。

/setFactory-log.png

从 Log 输出可以看出,实际上,你所有布局的控件,都会经过 LayoutInflaterFactory.onCreateView() 方法走一遍,去实现初始化的过程,在其中可以有效的分辨出是什么控件,以及它有什么属性。

并且 onCreateView() 方法的返回值,就是一个 View,如果要替换该 View,可以在此处将其初始化后返回回去即可。

三、利用 LayoutInflater 替换字体

既然原理都清楚了,那么我们接下来就开始实际操作一下,如何通过替换 LayoutInflaterFactory 来达到替换控件,从而达到替换字体的目的。

首先,定义一个 Activity 为基类,其中在 super..onCreate() 方法之前,调用 LayoutInflaterCompat.setFactory() ,然后将它的替换为 我们自己定义的 CustomFontCompatDelegate 类。

/demo-activitycode.png

CustomFontCompatDelegate 的实现,也非常的简单,只需要在它的 onCreateView() 方法中,替换掉 TextView 就可以。

/demo-delegate-code.png

其实,所有替换字体的逻辑,都在 FontTextView 中,接下来我们再看看 FontTextView 的逻辑。

/fontTextView.png

可以看到,在 FontTextView 中,直接完整的将字体替换成我们在 assets 目录下存放的 custom_font.ttf 字体文件。

到这里就完成了基本的功能,我们接下来看看如何使用它。

只需要使用一个 Activity ,继承我们刚才实现的 CustomFontActivity,然后写一个简单的布局,其中有三个 TextView。

/demo-activityxml.png

最后,我们再来看看运行后的效果。

/f-fontimage.png

四、小结

到这里基本上就介绍清楚如何通过 LayoutInflaterCompat.setFactory() 去替换 Factory 这个接口,达到我们替换控件的目的,从而完美的替换全局的字体。

但是实际开发过程中,依然需要考虑所有可以显示文字的控件,例如:TextView、EditText、Button 等等,这些都是我们需要重写的控件。

8b7f5b0bec734584e62ce6f5e7c103aa

点赞或者分享吧~

<think>嗯...用户在询问KotlinViewBinding中`binding!!.content`的具体含义。用户似乎已经有一定的Android开发基础,注意到ta引用了三篇关于ViewBinding的技术文章,还特别强调了"!!"操作符的用法。从引用内容来看:第一个引用提到ViewBinding能减少空指针异常(正好和!!相反呢)第二个引用展示了委托方式实现ViewBinding的高级用法(用户可能看过类似魔法代码)第三个引用说明kotlin-android-extensions插件已被淘汰(这点很重要需要强调)用户核心困惑应该是两个部分:非空断言操作符"!!"的危险性,以及.content属性到底是什么。考虑到用户能精准写出viewbinding的字段名,ta可能在实际编码中遇到了崩溃或者IDE警告。需要特别注意用户上次问题没提到setContentView,而引用[2]特别强调委托方式可以去除setContentView,这点值得展开说明。.content属性本质上是个布局容器(比如ConstraintLayout),但具体类型取决于XML布局的根视图。安全提示很关键,必须警告用户!!可能导致NPE崩溃,这点正好和引用[1]提到的ViewBinding优势形成矛盾——既然用了ViewBinding来避免空指针,就不该再用!!自相矛盾。</think> 在 Kotlin 中,`viewBinding!!.content` 的用法涉及两个关键技术点:**ViewBinding 机制**和 **Kotlin 的空安全操作符**。以下是详细解析: --- ### 一、`!!` 操作符的含义 - **非空断言**(`!!`):强制将可为空(`Nullable`)类型转换为非空类型 - **风险**:若对象实际为 `null` 会立即抛出 `NullPointerException` - **适用场景**:仅当 **100% 确定对象非空**时使用(否则应使用安全调用 `?.` 或空值处理) --- ### 二、`.content` 的具体含义 1. **来源**: ```kotlin val binding: ActivityMainBinding = ActivityMainBinding.inflate(layoutInflater) setContentView(binding.root) // binding.root 是整个布局的根视图 ``` 2. **`content` 的本质**: - `content` 是 **XML 布局中根布局的 ID** - 例如:若根布局是 `ConstraintLayout` 且设置了 `android:id="@+id/content"` ```xml <!-- activity_main.xml --> <androidx.constraintlayout.widget.ConstraintLayout android:id="@+id/content"> <!-- 子视图 --> </ConstraintLayout> ``` - 此时 `binding.content` 直接指向这个根布局对象 --- ### 三、典型应用场景 #### 场景 1:动态修改根布局属性 ```kotlin binding.content.setBackgroundColor(Color.RED) // 直接操作根视图 ``` #### 场景 2:添加全局监听 ```kotlin binding.content.setOnClickListener { Toast.makeText(this, "Root layout clicked", Toast.LENGTH_SHORT).show() } ``` --- ### 四、安全使用建议(避免 `!!`) #### ✅ 推荐写法 1:使用非空类型变量 ```kotlin private lateinit var binding: ActivityMainBinding override fun onCreate(savedInstanceState: Bundle?) { binding = ActivityMainBinding.inflate(layoutInflater) setContentView(binding.root) // 直接使用非空对象 binding.content.visibility = View.VISIBLE } ``` #### ✅ 推荐写法 2:安全调用 + 空值处理 ```kotlin binding?.content?.let { rootView -> rootView.alpha = 0.5f } ?: run { Log.e(TAG, "Binding is null") } ``` --- ### 五、技术演进注意 1. **废弃插件**:`kotlin-android-extensions`(合成视图)已由 Google 官方废弃[^3] 2. **迁移方案**: ```gradle // 替换 build.gradle 中的旧插件 apply plugin: 'kotlin-parcelize' // 替代 kotlin-android-extensions ``` 3. **最佳实践**:ViewBinding + 委托模式(参考引用 [2])可简化代码: ```kotlin // 委托方式示例(需自定义委托类) private val binding: ActivityMainBinding by viewBind() ``` --- ### 六、关键总结 | **语法** | **含义** | **风险** | |-------------------|----------------------------------|-----------------------| | `binding!!.content` | 强制获取非空 ViewBinding 中的根视图 | 若 binding 为 null 则崩溃 | | `binding?.content` | 安全访问根视图 | 返回可空类型 | | `lateinit binding` | 延迟初始化非空对象 | 未初始化前访问会崩溃 | > ⚠️ 切勿滥用 `!!`:ViewBinding 的设计初衷是 **减少空指针**[^1],使用 `!!` 与其理念相悖 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值