需求
App字体大小不变
如果用户将系统字体大小设置的非常大,可能导致APP的文字大小显示异常。
目标效果是,APP内字体大小不随系统设置的 字体大小
- 原始效果
系统的字体大小设置为 超大 时:(字体大小 可变)
- 目标效果
系统的字体大小设置为 超大 时:(字体大小 不变)
- 超大 ,指的是如下图所示的设置
解决方案
原理
在 Activity 初始化 Context 时,将 字体缩放比例(fontScale)始终设置为 1 。
实现方法
在基类 BaseActivity 中,重写 attachBaseContext()
说明
- 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) return
context.resources.configuration.let {
it.fontScale = 1f // 保持字体比例不变,始终为 1.
applyOverrideConfiguration(it) // 应用新的配置
}
}
applyOverrideConfiguration()方法只能被调用一次,且必须在 getResources() 和 getAssets() 前调用。
BaseActivity-Kotlin 完整代码
package com.example.fontscale
import 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) return
context.resources.configuration.let {
it.fontScale = 1f // 保持字体比例不变,始终为 1.
applyOverrideConfiguration(it) // 应用新的配置
}
}
}
Java 代码
@Override
protected 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 {
@Override
protected 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 属性,则对应的系统设置修改后,将进入此函数,不再重启 Activity
setFontScale()
}
/**
* 保持字体比例不变,始终为 1.
*/
private fun setFontScale() {
resources.configuration.fontScale = 1f
}
BaseActivity-Compose 完整代码
package com.example.fontscalecompose
import 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 属性,则对应的系统设置修改后,将进入此函数,不再重启 Activity
setFontScale()
}
/**
* 保持字体比例不变,始终为 1.
*/
private fun setFontScale() {
resources.configuration.fontScale = 1f
}
}
configChanges 配置方法
android:configChanges 配置方法
不推荐方法一:重写getResources()
原因
在Activity中重写 getResources(),此方法是不符合逻辑的,且可能造成性能问题。
getResources() 方法,会在页面获取资源时多次调用,而不是在页面初始化时调用一次。所以,在此处修改配置是不符合逻辑的。
另外,如果页面布局比较复杂,调用 getResources() 的次数就会非常多,在getResources()方法内耗费时间更新Configuration,可能会 造成卡顿 。
不推荐代码示例
@Override
public 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)