第35篇:字体缩放——无障碍适配实践

在这里插入图片描述

一、引言

无障碍适配是鸿蒙应用开发的重要课题,它关乎所有用户的使用体验,特别是视力不佳的用户群体。DriverLicenseExam 项目在 1.0.6 版本中新增了"答题字体跟随系统变化"功能,并在 1.0.7 版本中进一步优化了深色模式下的字体显示。本文将深入解析字体缩放功能的实现原理和最佳实践。

二、字体缩放的核心机制

2.1 系统字体缩放与 maxFontScale

在鸿蒙系统中,用户可以在"设置 → 显示和亮度 → 字体大小"中调整系统字体大小。应用需要响应这一变化,但有些场景需要限制最大缩放比例以避免布局溢出。

ArkUI 的 Text 组件提供了 maxFontScale 属性来控制字体最大缩放比例:

// 限制字体最大缩放比例为 1(不跟随系统缩放)
Text('广告标题')
  .fontSize(20)
  .maxFontScale(1);  // 不随系统缩放

// 允许跟随系统缩放,但限制最大 1.2 倍
Text('顺序练习')
  .fontSize(14)
  .maxFontScale(1.2);  // 最大缩放到 1.2 倍

// 不设置 maxFontScale,完全跟随系统
Text('题目内容')
  .fontSize(16);  // 会跟随系统缩放

2.2 项目中 maxFontScale 的使用场景

项目中有选择地使用了 maxFontScale

// 场景1:广告文案 - 不允许缩放(保持设计效果)
Text('学好驾车,安全你我')
  .fontSize(20)
  .fontColor('#FFFFFF')
  .maxFontScale(1);  // 广告文字不缩放

// 场景2:功能按钮 - 允许适度缩放
Text('顺序练习')
  .fontSize(14)
  .fontColor('#FFFFFF')
  .maxFontScale(1.2);  // 最大 1.2 倍

// 场景3:统计数字 - 允许缩放但保持布局
Text('练习进度')
  .fontSize(10)
  .maxFontScale(1.5);  // 小字可以放大更多

// 场景4:Tab 标签 - 不允许缩放
Text('首页')
  .fontSize(14)
  .maxFontScale(1);  // 标签栏文字不缩放

三、字体大小滑块实现

3.1 自定义字体缩放

除了跟随系统字体,项目还提供了应用内的字体大小调节功能,让用户可以在应用内独立控制字体大小:

// FontSizeSlider.ets
@ComponentV2
export struct FontSizeSlider {
  @Local fontSize: number = 1.0;  // 1.0 = 100% 缩放

  build() {
    Column() {
      // 标题
      Text('字体大小').fontSize(16).maxFontScale(1);

      // 预览文本
      Text('驾考模拟练习')
        .fontSize(Math.round(16 * this.fontSize))
        .fontColor($r('sys.color.font_primary'))
        .margin({ top: 16, bottom: 16 });

      // 滑块
      Slider({
        value: this.fontSize,
        min: 0.85,    // 最小 85%
        max: 1.15,    // 最大 115%
        step: 0.05,   // 步长 5%
      })
      .showTips(true)  // 显示当前值
      .onChange((value: number) => {
        this.fontSize = value;
        FontUtils.setFontScale(value);
      });

      // 大小标签
      Row() {
        Text('小').fontSize(12).maxFontScale(1);
        Blank();
        Text('标准').fontSize(12).maxFontScale(1);
        Blank();
        Text('大').fontSize(12).maxFontScale(1);
      }
      .width('100%')
      .margin({ top: 8 });
    }
    .padding(16)
    .width('100%');
  }
}

3.2 FontUtils 工具类

// FontUtils.ets
export class FontUtils {
  private static scale: number = 1.0;

  // 设置字体缩放比例
  static setFontScale(scale: number) {
    FontUtils.scale = scale;
    // 保存到 Preferences
    const preferencesUtil = PreferencesUtil.getInstance();
    const preference = preferencesUtil.getPreferences(context);
    preferencesUtil.preferencesPut(preference, 'fontScale', scale);
  }

  // 获取当前字体缩放
  static getFontScale(): number {
    return FontUtils.scale;
  }

  // 计算缩放后的字体大小
  static calculateSize(baseSize: number): number {
    return Math.round(baseSize * FontUtils.scale);
  }
}

四、鸿蒙字体使用

4.1 系统字体

项目使用了鸿蒙系统字体,确保在不同设备上显示一致:

// 使用鸿蒙系统字体
.fontFamily('HarmonyOS Sans')     // 英文字体
.fontFamily('鸿蒙黑体')            // 中文字体

4.2 字体大小单位

项目使用 fp 作为字体大小单位,这是鸿蒙推荐的字体单位,会自动跟随系统字体缩放:

// font.json 中定义字体大小
{ "name": "font_size_10", "value": "10fp" }
{ "name": "font_size_14", "value": "14fp" }
{ "name": "font_size_16", "value": "16fp" }
{ "name": "font_size_24", "value": "24fp" }

4.3 代码中的字体引用

// 引用资源中定义的字体大小
.fontSize($r('app.string.font_size_10'))
.fontSize($r('app.string.font_size_14'))
.fontSize($r('app.string.font_size_24'))

// 直接指定字体大小(不推荐)
.fontSize(14)

推荐使用 $r 引用资源文件中的字体大小,便于统一管理。

五、字体适配的挑战与解决方案

5.1 布局溢出问题

当字体放大时,文本可能超出容器边界。项目通过多种方式解决:

// 方案1:限制最大行数 + 省略号
Text('题目内容')
  .fontSize(16)
  .maxLines(2)  // 最多显示 2 行
  .textOverflow({ overflow: TextOverflow.Ellipsis });  // 超出显示省略号

// 方案2:使用 maxFontScale 限制最大缩放
Text('重要标签')
  .fontSize(14)
  .maxFontScale(1.2);  // 最大 1.2 倍

// 方案3:使用 minHeight 确保容器高度
Row() {
  Text('设置项').fontSize(16);
}
.minHeight(46)  // 最小高度保证触摸区域
.padding({ left: 12, right: 12 });

5.2 触摸区域适配

字体变大时,按钮的触摸区域也需要相应调整:

// 使用 minHeight 确保触摸区域
Button('提交')
  .minHeight(44)  // 最小触摸高度 44vp
  .fontSize(16);

六、字体缩放在不同场景的应用分析

6.1 考试页面的字体适配

考试页面是文字内容最密集的场景,需要特别注意字体适配:

// 考试题目:允许较大缩放,但限制最大行数
Text(currentQuestion.question)
  .fontSize(16)
  .maxFontScale(1.5)    // 允许放大到 1.5 倍
  .lineHeight(24)
  .maxLines(6)           // 最多显示 6 行
  .textOverflow({ overflow: TextOverflow.Ellipsis });

// 选项文本:跟随缩放,保证可读性
Text(optionText)
  .fontSize(14)
  .maxFontScale(1.5)
  .padding({ left: 12, right: 12 });

// 题号/进度:小字可以放大更多
Text('第 ' + (currentIndex + 1) + ' / ' + total + ' 题')
  .fontSize(12)
  .maxFontScale(2.0);   // 小字允许更大缩放比例

6.2 统计面板的字体适配

统计页面数字较大,需要平衡显示效果和缩放需求:

// 统计数字:大字号,限制缩放防止溢出
Text(String(this.didCount))
  .fontSize(24)
  .fontFamily('鸿蒙黑体')
  .fontWeight(FontWeight.Medium)
  .height(32)
  .maxFontScale(1.2)  // 大字号限制缩放
  .textAlign(TextAlign.Center);

// 统计标签:允许正常缩放
Text('已做题')
  .fontSize(14)
  .fontFamily('鸿蒙黑体')
  .maxFontScale(1.5);

6.3 底部 Tab 栏的字体适配

Tab 栏空间有限,需要严格限制缩放:

// Tab 标签:不允许缩放,保持布局稳定
Text(item.label)
  .fontColor(this.vm.curIndex === index ?
    $r('app.color.icon_color_highlight') : $r('sys.color.font_secondary'))
  .fontSize($r('app.string.font_size_10'))
  .maxFontScale(1);  // 标签栏文字不缩放

七、无障碍适配最佳实践

6.1 无障碍检查清单

  • 所有文本使用 fp 单位
  • 关键文本设置 maxFontScale 限制
  • 广告/装饰性文本设置 maxFontScale(1)
  • 按钮使用 minHeight 保证触摸区域
  • 容器使用 minHeight 而非固定高度
  • 文本超出时使用 textOverflow 处理
  • 图片使用 objectFit 控制缩放

6.2 用户偏好持久化

// 字体缩放偏好持久化
aboutToAppear() {
  const preferencesUtil = PreferencesUtil.getInstance();
  const preference = preferencesUtil.getPreferences(context);
  const savedScale = preferencesUtil.getPreferencesValue(preference, 'fontScale');
  if (savedScale) {
    FontUtils.setFontScale(Number(savedScale));
  }
}

七、总结

字体缩放适配是鸿蒙应用无障碍设计的重要组成部分。DriverLicenseExam 项目通过以下方式实现了良好的字体适配:

  1. maxFontScale 控制:为不同场景设置不同的最大缩放比例
  2. 字体大小滑块:应用内独立调节字体大小
  3. fp 单位:使用鸿蒙推荐的字体单位
  4. 布局保护:通过 minHeight、maxLines 等属性防止布局溢出
  5. 偏好持久化:通过 Preferences 保存用户字体偏好

关键源码文件:

  • components/app_setting/src/main/ets/components/FontSizeSlider.ets — 字体大小滑块
  • components/app_setting/src/main/ets/common/FontUtils.ets — 字体工具类
  • AppScope/resources/base/element/font.json — 字体大小定义
  • commons/commonLib/src/main/ets/utils/PreferencesUtil.ets — 偏好持久化
Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐