需求

App字体大小不变

如果用户将系统字体大小设置的非常大,可能导致APP的文字大小显示异常。

目标效果是,APP内字体大小不随系统设置的 字体大小

  • 原始效果

系统的字体大小设置为 超大 时:(字体大小 可变)

android spinner 字体内边距 android字体大小_android

  • 目标效果

系统的字体大小设置为 超大 时:(字体大小 不变)

android spinner 字体内边距 android字体大小_ide_02

  • 超大 ,指的是如下图所示的设置

android spinner 字体内边距 android字体大小_android_03


解决方案

原理

在 Activity 初始化 Context 时,将 字体缩放比例(fontScale)始终设置为 1

实现方法

在基类 BaseActivity 中,重写 attachBaseContext()

说明

  1. attachBaseContext() 方法,在 onCreate() 方法之前调用,Activity获取构建页面所需的上下文配置时调用此方法。
  2. 在 attachBaseContext() 中,修改Configuration的参数,可以实现整个页面配置的修改;修改Configuration.fontScale,也就能实现字体大小的控制。
  3. 所有Activity均需要继承BaseActivity。因为将 fontScale 配置修改放置在了BaseActivity中,可以对APP的所有Activity页面生效。
  4. 当 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将不会重启,而是直接从后台进入前台,页面字体将直接变成 超大

解决方案

attachBaseContextonConfigurationChanged 中,设置 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 配置方法

android spinner 字体内边距 android字体大小_android_04


不推荐方法一:重写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() 方法调用的日志

android spinner 字体内边距 android字体大小_Android_05

Resources.updateConfiguration() 已废弃

此方法已废弃,不应再使用。

android spinner 字体内边距 android字体大小_android_06


不推荐方法二:sp 换成 dp

将布局文件中 android:textSize 属性的字号单位 sp 换成 dp。这种方式是 错误的 

原因

  1. 将 sp 改为 dp,需要将所有用到 textSize 属性的控件全部进行修改,基本意味着项目中的所有布局文件都要修改,这明显不合理。
  2. sp 是Android推荐的字号单位,在 textSize 中使用其他类型的单位,Android Studio 是有 Warning 警告的。

Warning 警告示例

android spinner 字体内边距 android字体大小_Android_07

尺寸 官方说明

尺寸的官方文档

android spinner 字体内边距 android字体大小_android_08


参考

Configuration 官方说明

Configuration  |  Android Developers (google.cn)

android spinner 字体内边距 android字体大小_Android_09

attachBaseContext 官方说明

Activity  |  Android Developers (google.cn)

applyOverrideConfiguration 官方说明

ContextThemeWrapper  |  Android Developers (google.cn)

createConfigurationContext 官方说明

Context  |  Android Developers (google.cn)