安卓字体不随系统字体变化

sticker

emmmm... 最近做了个优化,记录一下,毕竟网上查到的有点问题。

StackOverFlow 上看到有类似的提问:
Font size of TextView in Android application changes on changing font size from native settings

但是回答都是一样的:把单位dp换成sp,里面甚至有关于替换后大小不受系统字体变化影响的原理说明:
Android sp vs dp texts - what would adjust the 'scale' and what is the philosophy of support

dp sp 的替换显然不适用于我现在的情况,尤其是这样的替换,在 webview 中是不适用的。百度能看到的答案也千篇一律,大多是这样的方法 (重写 Application 或 BaseActivity 中的方法):

/**
 * 重写 getResource 方法,防止系统字体影响
 */
@Override
public Resources getResources() {
    Resources resources = super.getResources();
    Configuration configuration = new Configuration();
    configuration.setToDefaults();
    resources.updateConfiguration(configuration, resources.getDisplayMetrics());
    return resources;
}

但是上面的方法存在两个问题:

  • 使用的 Configuration 是新建的对象,从代码看应该是没有问题的。但是项目上应用的时候,使用第三方 sdk 出现了空指针。错误出现在 Locale.toString() ,是在 resources.updateConfiguration(configuration, resources.getDisplayMetrics()) 的调用过程中出现的问题。这里的 Locale 应该是 Configuration 中属性 locale 。应该是系统中原本自带的属性遗失了。在大多数的情况下,我们不会察觉到这样的差异,因为在 Context#updateConfiguration 中对于没有的值都赋予了默认值。
public void updateConfiguration(Configuration config,
        DisplayMetrics metrics, CompatibilityInfo compat) {
    synchronized (mAccessLock) {
        //其他代码
        
        int configChanges = 0xfffffff;
        if (config != null) {
            //储存新的 config
            mTmpConfig.setTo(config);
            
            //local 默认值赋值
            if (mTmpConfig.locale == null) {
                mTmpConfig.locale = Locale.getDefault();
                mTmpConfig.setLayoutDirection(mTmpConfig.locale);
            }
            
            //mConfiguration 赋值
            configChanges = mConfiguration.updateFrom(mTmpConfig);
        }
        
        //其他代码
     }
}
  • configuration.setToDefaults() 会把所有属性都设置成系统默认,对于有特殊需求的 app 来说是不合理的,应该只修改字体大小的设置。

对于第一个问题,我尝试不用新建的 Configuration 而是利用 Resource#getConfiguration 获取的话,就不再报错。对于第二个问题,我们可以只设置 configuration.fontScale属性。

综上,得到下面的代码:

/**
 * 重写 getResource 方法,防止系统字体影响
 */
@Override
public Resources getResources() {
    Resources resources = super.getResources();
    if (resources != null && resources.getConfiguration().fontScale != 1) {
        Configuration configuration = resources.getConfiguration();
        configuration.fontScale = 1;
        resources.updateConfiguration(configuration, resources.getDisplayMetrics());
    }
    return resources;
}

2018-4-2 补充


上面的代码在大部分逻辑上都已经走通(除了带界面的第三方 sdk)。最近发现一个新的问题,如果页面上有 TextWatcher 文本监听,字体变化似乎会触发它的监听方法,原本不需要判定是否为空的许多变量,在现在就变得不可控制了。因为当文字字体大小改变时,整个页面会从 onCreate 开始重新加载。为此,我全局检索了所有使用到 TextWatcher 的地方,加上非空判断,同时在所有页面上加上了字体变化的监听,使得在字体变化的时候调用 onConfigurationChange 方法,而不是重建页面,这一点处理和旋转屏幕类似。

//清单文件中添加 android:configChanges 属性
<activity
    android:name=".xxx.TestActivity"
    android:configChanges="fontScale" />

另外,如果通过updateConfiguration方法进行更新,我们会发现,每一次调用getResource方法时,Context.getResources.getConfiguration.fontScale的值并不会变化,依然是我们系统设置中的字体大小,也就是说,新的 Configuration 并没有更新到 Context 中去。这样频繁调用 updateConfiguration效率太差。

查看 API 可以看到 updateConfiguration 在 API 25 之后,被方法 createConfigurationContext
替代了,那么是不是可以通过 createConfigurationContext 得到更好的解决方案呢?

createConfigurationContext
<p/>
Return a new Context object for the current Context but whose resources are adjusted to match the given Configuration. Each call to this method returns a new instance of a Context object; Context objects are not shared, however common state (ClassLoader, other Resources for the same configuration) may be so the Context itself can be fairly lightweight.

重点已经给出,可以看到通过这个方法,我们可以把新的configuration 应用到 Context 中去,这种情况下,我们可以看到 Configuration.fontScale 在大部分时候已经返回 "1" 了。

public Resources getResources() {
    Resources resources = super.getResources();
    Configuration newConfig = resources.getConfiguration();

    if (resources != null && newConfig.fontScale != 1) {
        newConfig.fontScale = 1;
        if (Build.VERSION.SDK_INT >= 17) {
            Context configurationContext = createConfigurationContext(newConfig);
            resources = configurationContext.getResources();
        } else {
            resources.updateConfiguration(newConfig, displayMetrics);
        }
    }
    return resources;
}

上面的代码虽然改变了 Configuration 中的 fontScale 属性 ,但是实际显示的字体大小依旧没有变化,我们需要更新配置。怎么更新可以参考 updateConfiguration 中的代码:

public void updateConfiguration(Configuration config,
        DisplayMetrics metrics, CompatibilityInfo compat) {
        synchronized (mAccessLock) {
            /**
             * A scaling factor for fonts displayed on the display.  This is the same
             * as {@link #density}, except that it may be adjusted in smaller
             * increments at runtime based on a user preference for the font size.
             */
            mMetrics.scaledDensity = mMetrics.density * mConfiguration.fontScale;
        }
    }
}

综上,获得下面的代码:

@override
public Resources getResources() {
    Resources resources = super.getResources();
    Configuration newConfig = resources.getConfiguration();
    DisplayMetrics displayMetrics = resources.getDisplayMetrics();

    if (resources != null && newConfig.fontScale != 1) {
        newConfig.fontScale = 1;
        if (Build.VERSION.SDK_INT >= 17) {
            Context configurationContext = createConfigurationContext(newConfig);
            resources = configurationContext.getResources();
            displayMetrics.scaledDensity = displayMetrics.density * newConfig.fontScale;
        } else {
            resources.updateConfiguration(newConfig, displayMetrics);
        }
    }
    return resources;
}
谢谢观赏
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,716评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,558评论 1 294
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,431评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,127评论 0 209
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,511评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,692评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,915评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,664评论 0 202
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,412评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,616评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,105评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,424评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,098评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,096评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,869评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,748评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,641评论 2 271

推荐阅读更多精彩内容