18、字体与缩放:系统字体缩放影响、SP与DP的合理使用

做TV大屏开发,有个坑我踩过不止一次——字体缩放。

手机开发里,用户改个系统字体大小,界面顶多布局撑开一点。但在TV上,用户一调大字体,整个UI可能直接崩掉。我接手过一个项目,用户反馈“设置页面按钮点不到”,查了半天,原来是系统字体调到了最大,按钮文字溢出,焦点都跑偏了。

这一章,咱们就把字体缩放这件事彻底聊透。

系统字体缩放到底影响了什么?

Android系统有个“字体大小”设置,藏在「设置 → 显示 → 字体大小」里。它影响的是所有使用 sp 单位的文本。

默认缩放比例是1.0。用户调到“大”就是1.15,“超大”可能是1.3。你想想看,如果布局里一个按钮宽度写死了200dp,但文字用了18sp,用户一调大字体,文字宽度变成18×1.3=23.4sp对应的物理宽度,按钮装不下了。

为什么会这样?因为 spdp 的换算逻辑不同:

单位 基准 受字体缩放影响 典型用途
dp 160dpi 布局尺寸、间距、控件宽高
sp 160dpi 字体大小

说白了,sp = dp × 用户缩放系数。这个系数在TV上尤其危险,因为TV用户经常远距离观看,调大字体的比例比手机用户高得多。

SP与DP的合理使用原则

我个人习惯遵循三条铁律:

  1. 文字用sp,其他全用dp——这是官方推荐,也是底线。
  2. 布局容器不要依赖文字撑开——按钮、卡片、列表项的高度用dp固定,文字在内部自适应。
  3. 关键文字用dp兜底——比如标题、标签、按钮文字,如果业务上绝对不能溢出,就用dp。

嗯,这里要注意第三条。有些设计师会说“标题必须用sp,否则用户调大字体没变化”。但TV上用户调大字体是为了看清内容,不是为了让标题占满屏幕。我一般这样处理:正文内容用sp,标题和按钮文字用dp。用户能看清正文就够了,界面结构不能乱。

代码层面的最佳实践

先看一个反面例子:

<!-- 错误做法:按钮高度依赖文字撑开 -->
<Button
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:textSize="18sp"
    android:padding="8dp" />

用户字体调到1.3倍时,按钮高度从34dp(18sp+16dp padding)变成18×1.3+16=39.4dp。如果周围布局是精确对齐的,就可能重叠。

正确做法:

<!-- 正确做法:固定高度,文字自适应 -->
<Button
    android:layout_width="wrap_content"
    android:layout_height="48dp"
    android:textSize="18sp"
    android:gravity="center" />

这样按钮高度固定48dp,文字再大也不会撑破布局。文字溢出怎么办?用 ellipsize 或者限制最大字号。

限制最大字号的实用方案

我在项目中遇到过一个问题:用户把字体调到最大,一个“确认”按钮的文字变成了“确…”,因为按钮宽度有限。后来我写了个工具方法:

public static float getLimitedTextSize(Context context, float originalSp) {
    float fontScale = context.getResources().getConfiguration().fontScale;
    float maxScale = 1.2f; // 最大允许缩放1.2倍
    if (fontScale > maxScale) {
        return originalSp * maxScale;
    }
    return originalSp * fontScale;
}

使用时:

textView.setTextSize(TypedValue.COMPLEX_UNIT_SP, 
    FontUtil.getLimitedTextSize(context, 18f));

这样用户就算调到1.3倍,实际显示也只放大到1.2倍。既照顾了可读性,又保住了布局。

TV大屏的特殊考量

TV上还有个手机没有的问题——焦点放大。很多TV Launcher在焦点移到某个控件时,会放大该控件(包括文字)。如果文字用了sp,焦点放大+系统字体放大=文字可能超出控件边界。

我曾经调试过一个Bug:用户把字体调到“大”,焦点移到“设置”按钮时,文字直接跑到了按钮外面。原因是焦点动画把控件放大了1.1倍,系统字体又放大了1.15倍,叠加后文字尺寸变成了18×1.1×1.15=22.77sp,而按钮的放大动画只针对背景,没同步放大padding。

解决方案:焦点动画里同时放大控件的padding,或者干脆用dp固定文字大小。

SVG:字体缩放影响链路图

系统字体缩放影响链路 用户调整字体大小 Configuration.fontScale 变化 所有使用 sp 单位的 TextView 文字大小被缩放 布局有弹性 → 正常显示 布局固定 → 文字溢出/重叠 关键:dp 单位不受影响,sp 单位受 fontScale 影响

实战中的避坑指南

我曾经因为字体缩放问题被用户投诉过。那是一个视频详情页,标题用了sp,用户调大字体后,标题和描述文字重叠了。后来我加了个全局的字体缩放监听:

@Override
public void onConfigurationChanged(Configuration newConfig) {
    super.onConfigurationChanged(newConfig);
    float scale = newConfig.fontScale;
    if (scale > 1.15f) {
        // 字体过大时,改用dp单位重新设置
        titleView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 20);
        descView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14);
    }
}

但这个方法有个缺点——用户切回默认字体时,文字不会自动恢复sp。所以更好的做法是在基类里统一管理:

public class BaseTvActivity extends Activity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // 强制使用dp,忽略系统字体缩放
        getResources().getConfiguration().fontScale = 1.0f;
    }
}

嗯,这个方案比较粗暴,但确实有效。适合对UI一致性要求极高的TV应用。

总结几个关键点

  • sp和dp混用是常态,但要有明确边界:文字用sp,布局用dp。
  • TV上建议限制最大缩放比例,1.2倍是个安全值。
  • 焦点放大和字体缩放叠加是TV特有的坑,务必测试。
  • 关键UI元素(标题、按钮、标签)可以考虑用dp固定文字大小。
  • 全局监听Configuration变化,在字体过大时主动干预。

字体缩放这件事,说大不大,说小不小。但一旦出问题,用户感知非常明显——文字跑出按钮、界面错位、焦点丢失,每一个都是体验灾难。你想想看,用户只是想看清字,结果界面崩了,这多闹心。

所以我的建议是:开发阶段就把字体调到最大测一遍,别等用户来帮你测。

核心原则:TV大屏上,布局结构比文字大小更重要。用户调大字体是为了看清内容,不是为了破坏你的UI。用dp保结构,用sp保体验,两者平衡才是正道。

小技巧:在AndroidManifest.xml里给Activity加上 android:configChanges="fontScale",可以避免Activity重建,自己控制字体缩放逻辑。

警告:不要在所有地方都用dp代替sp。系统字体缩放是辅助功能,完全禁用会让部分用户无法正常使用。正确的做法是限制缩放范围,而不是一刀切禁用。

公众号:蓝海资料掘金营,微信deep3321