阅读 4426

Android修炼系列(二十),由系统字号来调整 App 字体大小

在平时开发中,一般 App 的界面布局都只会适配标准字体的尺寸,如果用户在设置中修改了系统字号大小,那么 App 的 UI 就有可能惨不忍睹。

针对这种情况,很多 App 都会选择放弃使用系统字号,方法也很简单,直接在我们的 BaseActivity 中修改 fontScale:

    @TargetApi(17)
    override fun getResources(): Resources {
        val resources = super.getResources();
        val configContext = createConfigurationContext(resources.configuration)

        return configContext.resources.apply {
            configuration.fontScale = 1.0f
            displayMetrics.scaledDensity = displayMetrics.density * configuration.fontScale
        }
    }
复制代码

在低版本中,我们还能这样设置,实测没啥问题,不过 updateConfiguration 方法已经处于废弃状态:

    override fun getResources(): Resources {
        return super.getResources().apply {
            configuration.fontScale = 1.0f
            updateConfiguration(configuration, displayMetrics)
        }
    }
复制代码

sp 转 dp

将 sp 改为 dp 也可以保持字体缩放不变。

顺便说一嘴,标注尺寸时,sp 与dp 关系:字体标准100%时 1sp=1dp,而字体标准大于100%时,1sp 就会大于 1dp。

bbb.png

sp:与缩放无关的抽象像素,sp 与 dp 一样是物理像素,但 Android 系统允许用户自定义文字尺寸大小,因此 sp 能根据系统设置而呈现出不同效果,这也许就是规范里,文字尺寸要使用 sp 的原因吧。

dp:设备独立像素,它只与设备屏幕有关,所以 dp 标注的文字就不会随着系统字号的变化而变化了。但这样也会产生一个问题,如果 App 自身没有内置字号调整功能,又不受系统字号影响,那么对于部分用户群体来说,是非常不友好的。

字号缩放

通过上面的介绍,字号调整功能呼之欲出了,我们可以修改 configuration.fontScale 的值来定义字号的缩放倍数。

代码就不贴了,运行了下是 ok 的。但是这样会产生一些问题:

  • 如果文字控件没有自适应高度,就会导致文字显示不全;

  • fontScale 只能修改字体大小,其他 UI 元素不能跟随改变,这点就很难接受了,因为设完后你会发现整个 UI 都变得不协调了。

针对这种情况,另一种很巧妙的方案就应势而生了。

修改 density

  • dpi(每英寸的点数): 像素密度,是屏幕单位面积内的像素数。它与分辨率不同,分辨率是屏幕上的总像素数。

  • density: 当前像素密度指定将 dp 单位转换为像素时所必须使用的缩放系数,density = dpi/160,并且 px = dp * density。

有些抽象吧,再多说一嘴,我们都知道,android 支持不同的像素密度 dpi 模式,具体见下:

888.png

举个栗子,由上面定义可知,在 hdpi 模式下:1dp=1.5px,也就是说设计师在 PS 里定义一个view 高 72px,开发就应该定义该 view 高 48dp,Photoshop 中 21px 大的字体,开发会定义为 14sp(标准字体下,sp=dp)。

所以想一想,在 hdpi 模式下,假设我的 viewHeight=1dp,现在我不想 1dp=1.5px 了,我想 1dp=1.75px 了,是不是等比例放大 dpi 和 density 就可以了呢?这样界面的 UI 元素是不是也就等比例适配了呢。BaseActivity 代码见下:

    private fun setDensity() {
        val systemMetrics = getSystemMetrics()
        val scale = 1.0f // 根据需求定义系数
        with(resources.displayMetrics) {
            density = systemMetrics.density * scale
            scaledDensity = systemMetrics.density * scale
            densityDpi = (systemMetrics.densityDpi * scale).toInt()
        }
    }

    private fun getSystemMetrics(): DisplayMetrics {
        return applicationContext.resources.displayMetrics;
    }
复制代码

这里的 scaledDensity 需要注意,是将 fontScale=1 情况处理了,以适配 sp,当调整系统字体大小时 scaledDensity 实际值见下:

    /* 标准字体时,fontScale=1,非标准时,fontScale!=1 */
    displayMetrics.scaledDensity = displayMetrics.density * configuration.fontScale
复制代码

好了,到这里思路基本完了,代码虽然简单,但这个思想涉及到的细节还是值得琢磨的, gitHub 示例

问题

使用这个方案后,经过测试,还是有几点需要注意适配的:

  1. 页面宽高适配:当页面整体 UI 元素都放大后,会存在当前页面存放不下全部元素了的情况,这个时候就需要添加 Scroll 滚动效果了;横向的话,可根据需要,显示不够“..”。

  2. 部分元素不缩放:如果在当前页面,我想部分元素保持原始大小,不进行缩放呢?如上面所说的横向显示不够".."情况,如果这个文本是很重要的,使用“..”显然是不合适的。这时,我们可以获取元素原始大小,再动态设置:

    open fun dip2px(context: Context, dpValue: Float): Int {
        val scale = context.applicationContext.resources.displayMetrics.density
        return (dpValue * scale + 0.5f).toInt()
    }
复制代码

本文到这里就结束了。如果本文对你有用,来点个赞吧,大家的肯定也是道爷的动力。

文章分类
Android
文章标签