需求
App字体大小不变
如果用户将系统字体大小设置的非常大,可能导致APP的文字大小显示异常。
目标效果是,APP内字体大小不随系统设置的 字体大小 变化。
- 原始效果
系统的字体大小设置为 超大 时:(字体大小 可变)
- 目标效果
系统的字体大小设置为 超大 时:(字体大小 不变)
- 系统设置中,字体大小设置为 超大 ,指的是如下图所示的设置
示例项目(Gitee开源)
Android/FontScale · 宋冠巡/示例 - 码云 - 开源中国 (gitee.com)
Android/FontScaleJava · 宋冠巡/示例 - 码云 - 开源中国 (gitee.com)
Android/FontScaleCompose · 宋冠巡/示例 - 码云 - 开源中国 (gitee.com)
解决方案
原理
在 Activity 初始化 Context 时,将 字体缩放比例(fontScale)始终设置为 1 。
实现方法
在基类 BaseActivity 中,重写 attachBaseContext() 方法,设置 Configuration.fontScale = 1f
说明
- attachBaseContext() 方法,在 onCreate() 方法之前调用,Activity获取构建页面所需的上下文配置时调用此方法。
- 在 attachBaseContext() 中,修改Configuration的参数,可以实现整个页面配置的修改;修改Configuration.fontScale,也就能实现字体大小的控制。
- 所有Activity均需要继承BaseActivity。因为将 fontScale 配置修改放置在了BaseActivity中,可以对APP的所有Activity页面生效。
- 当 android:configChanges="fontScale" 后,仍能够正常实现字体大小不变。
android:configChanges:指定Activity将自行处理的一个或多个配置更改。如果未指定,则如果系统中发生任何这些配置更改,将重新启动Activity 。
当 configChanges 设置为 fontScale 后,后台App重新进入前台时,Activity将不会重启,而是触发 onConfigurationChanged 后直接从后台进入前台。
注意
请注意,本方法不适用于 Compose 项目。
Compose项目字体大小不变的办法,请参考后面的《Compose实现》章节。
核心代码
Kotlin 代码
override fun attachBaseContext(newBase: Context?) {super.attachBaseContext(newBase)overrideFontScale(newBase)}/*** 重置配置 fontScale:保持字体比例不变,始终为 1.*/private fun overrideFontScale(context: Context?) {if (context == null) returncontext.resources.configuration.let {it.fontScale = 1f // 保持字体比例不变,始终为 1.applyOverrideConfiguration(it) // 应用新的配置}}
applyOverrideConfiguration()方法只能被调用一次,且必须在 getResources() 和 getAssets() 前调用。
BaseActivity-Kotlin 完整代码
package com.example.fontscaleimport android.content.Context
import androidx.appcompat.app.AppCompatActivity/*** 所有Activity的基类*/
open class BaseActivity : AppCompatActivity() {override fun attachBaseContext(newBase: Context?) {super.attachBaseContext(newBase)overrideFontScale(newBase)}/*** 重置配置 fontScale:保持字体比例不变,始终为 1.*/private fun overrideFontScale(context: Context?) {if (context == null) returncontext.resources.configuration.let {it.fontScale = 1f // 保持字体比例不变,始终为 1.applyOverrideConfiguration(it) // 应用新的配置}}}
Java 代码
@Overrideprotected void attachBaseContext(Context newBase) {super.attachBaseContext(newBase);overrideFontScale(newBase);}/*** 重置配置 fontScale:保持字体比例不变,始终为 1.*/private void overrideFontScale(Context context) {if (context == null) return;Configuration configuration = context.getResources().getConfiguration();configuration.fontScale = 1f;applyOverrideConfiguration(configuration);}
BaseActivity-Java 完整代码
package com.example.fontscalejava;import android.content.Context;
import android.content.res.Configuration;import androidx.appcompat.app.AppCompatActivity;/*** 所有Activity的基类*/
public class BaseActivity extends AppCompatActivity {@Overrideprotected void attachBaseContext(Context newBase) {super.attachBaseContext(newBase);overrideFontScale(newBase);}/*** 重置配置 fontScale:保持字体比例不变,始终为 1.*/private void overrideFontScale(Context context) {if (context == null) return;Configuration configuration = context.getResources().getConfiguration();configuration.fontScale = 1f;applyOverrideConfiguration(configuration);}}
Compose实现
Compose项目,经过测试,在 attachBaseContext 中,applyOverrideConfiguration 无效;
并且,如果设置了 android:configChanges,也需要在 onConfigurationChanged 重新配置 fontScale。否则,字体大小配置修改后,当后台App重新进入前台时,Activity将不会重启,而是直接从后台进入前台,页面字体将直接变成 超大。
解决方案
在 attachBaseContext 和 onConfigurationChanged 中,设置 getResource().getConfiguration().fontScale 的值始终为 1.
核心代码
override fun attachBaseContext(newBase: Context?) {super.attachBaseContext(newBase)setFontScale()}override fun onConfigurationChanged(newConfig: Configuration) {super.onConfigurationChanged(newConfig)// 如果 Activity 配置了 android:configChanges 属性,则对应的系统设置修改后,将进入此函数,不再重启 ActivitysetFontScale()}/*** 保持字体比例不变,始终为 1.*/private fun setFontScale() {resources.configuration.fontScale = 1f}
BaseActivity-Compose 完整代码
package com.example.fontscalecomposeimport android.content.Context
import android.content.res.Configuration
import androidx.activity.ComponentActivity/*** 所有Activity的基类*/
open class BaseActivity : ComponentActivity() {override fun attachBaseContext(newBase: Context?) {super.attachBaseContext(newBase)setFontScale()}override fun onConfigurationChanged(newConfig: Configuration) {super.onConfigurationChanged(newConfig)// 如果 Activity 配置了 android:configChanges 属性,则对应的系统设置修改后,将进入此函数,不再重启 ActivitysetFontScale()}/*** 保持字体比例不变,始终为 1.*/private fun setFontScale() {resources.configuration.fontScale = 1f}}
configChanges 配置方法
android:configChanges 配置方法,参考如下博客
Activity生命周期_宋冠巡的博客-CSDN博客
不推荐方法一:重写getResources()
原因
在Activity中重写 getResources(),此方法是不符合逻辑的,且可能造成性能问题。
getResources() 方法,会在页面获取资源时多次调用,而不是在页面初始化时调用一次。所以,在此处修改配置是不符合逻辑的。
另外,如果页面布局比较复杂,调用 getResources() 的次数就会非常多,在getResources()方法内耗费时间更新Configuration,可能会 造成卡顿 。
不推荐代码示例
@Overridepublic Resources getResources() {Resources resources = super.getResources();Configuration configuration = resources.getConfiguration();// 设置字体大小不随系统设置变化。字体大小(字体比例)始终设置为默认值if (configuration.fontScale != 1) { // 字体大小(字体比例)为非默认值configuration.fontScale = 1; // 字体大小(字体比例)设置为默认值resources.updateConfiguration(configuration, resources.getDisplayMetrics());}return resources;}
getResources()调用日志
- 进入页面时,getResources() 方法调用的日志
Resources.updateConfiguration() 已废弃
此方法已废弃,不应再使用。
不推荐方法二:sp 换成 dp
将布局文件中 android:textSize 属性的字号单位 sp 换成 dp。这种方式是 错误的 。
原因
- 将 sp 改为 dp,需要将所有用到 textSize 属性的控件全部进行修改,基本意味着项目中的所有布局文件都要修改,这明显不合理。
- sp 是Android推荐的字号单位,在 textSize 中使用其他类型的单位,Android Studio 是有 Warning 警告的。
Warning 警告示例
尺寸 官方说明
尺寸的官方文档
参考
Configuration 官方说明
Configuration | Android Developers (google.cn)
attachBaseContext 官方说明
Activity | Android Developers (google.cn)
applyOverrideConfiguration 官方说明
ContextThemeWrapper | Android Developers (google.cn)
createConfigurationContext 官方说明
Context | Android Developers (google.cn)