Android适配经验Android-View

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

2018-03-23  本文已影响0人  Arnold_J
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;
}

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

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 而是利用 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;
}
谢谢观赏
上一篇 下一篇

猜你喜欢

热点阅读