一、字体设置为啥重要

字体是用户界面核心视觉元素之一,直接影响应用美观性、可读性和用户体验。

ArkUI 提供的字体控制能力:

  • 自定义设置字体大小和字重
  • 通过 registerFont() 注册 TTF 和 OTF 自定义字体文件
  • 下载字体到沙箱内注册,实现动态切换
  • 省略号、自动缩放、换行控制等文本溢出处理

本文讲四个场景:

  1. 使用自定义字体显示文本
  2. 从自定义字体恢复为系统字体
  3. 字体大小跟随系统设置
  4. 字体大小不跟随系统设置

二、使用自定义字体显示文本

场景咋回事

字体设置里,点击选择字体列表中某个字体,页面字体样式变化。退出应用重新进入,默认显示退出前选择的字体样式。

实现原理

registerFont() 在字体管理器注册自定义字体,支持 TTF 和 OTF 格式。

preferences(用户首选项)处理 Key-Value 数据,支持持久化轻量级数据,以及修改和查询。

开发步骤

第一步:创建首选项工具类

定义三个方法:

  • getTextFontPreference():获取首选项实例
  • saveModifyFont():保存字体信息到首选项
  • getFont():从首选项获取字体数据
export class PreferenceUtils {
  preference?: preferences.Preferences;

  // Get Preferences instance
  getTextFontPreference(context: Context) {
    try {
      this.preference = preferences.getPreferencesSync(context, { name: 'TextFontPreference' });
      hilog.info(0x0000, TAG, 'create preference success');
    } catch (err) {
      let error = err as BusinessError;
      hilog.error(0x0000, TAG, `create preference failed. code: ${error.code}, message:${err.message}`);
    }
  }

  // Save Font
  saveModifyFont(textFont: string) {
    try {
      this.preference?.putSync(TEXT_FONT, textFont);
      this.preference?.flush((err: BusinessError) => {
        if (err) {
          hilog.error(0x0000, TAG, `Failed to flush. code:${err.code}, message:${err.message}`);
          return;
        }
        hilog.info(0x0000, TAG, 'Succeeded in flushing.');
      })
    } catch (err) {
      let error = err as BusinessError;
      hilog.error(0x0000, TAG,
        `putSync or flush font preference data failed. code: ${error.code}, message:${err.message}`);
    }
  }

  // Get Font
  getFont(): string {
    let textFont: string = '';
    try {
      textFont = this.preference?.getSync(TEXT_FONT, '') as string;
    } catch (err) {
      let error = err as BusinessError;
      hilog.error(0x0000, TAG, `getSync font preference data failed. code: ${error.code}, message:${err.message}`);
    }
    return textFont;
  }
}

export default new PreferenceUtils();

第二步:EntryAbility 里获取首选项实例

onCreate() 生命周期调用:

export default class EntryAbility extends UIAbility {
  onCreate(_want: Want, _launchParam: AbilityConstant.LaunchParam): void {
    // Get preference instance
    PreferenceUtils.getTextFontPreference(this.context);
  }
}

第三步:定义注册字体方法

通过 UIContextgetFont() 获取 Font 对象,调用 registerFont() 注册字体:

// Register font
export function registerMyFont(uiContext: UIContext) {
  try {
    // Register HarmonyOS Italic font
    uiContext.getFont().registerFont({
      familyName: $r('app.string.HarmonyOS_Italic'),
      familySrc: $rawfile('HarmonyOS_SansItalic.ttf')
    });
    // Register HarmonyOS Condensed font
    uiContext.getFont().registerFont({
      familyName: $r('app.string.HarmonyOS_Condensed'),
      familySrc: $rawfile('HarmonyOS_Condensed.ttf')
    });
  } catch (err) {
    let error = err as BusinessError;
    hilog.error(0x0000, TAG, `registerFont failed. code: ${error.code}, message:${err.message}`);
  }
}

registerFont 参数:

  • familyName: 字体名称(用 $r 引用字符串资源)
  • familySrc: 字体文件路径(用 $rawfile 引用 rawfile 目录下的字体文件)

第四步:页面加载时注册字体并读取首选项

@StorageLink 装饰变量 fontOffset,标识当前选择的字体。页面 aboutToAppear() 里注册字体并读取首选项:

@StorageLink('fontOffset') fontOffset: string = '';

aboutToAppear() {
  // Get font data from preferences
  this.fontOffset = PreferenceUtils.getFont();
  // Register font
  registerMyFont(this.getUIContext());
}

第五步:点击 MenuItem 保存字体选择

MenuItem 的 onChange() 事件里修改 fontOffset 并保存到首选项:

MenuItem({
  content: item === '' ? $r('app.string.system_default') : item,
  endIcon: this.fontOffset === item ? $r('app.media.checkmark') : ''
})
  .onChange(() => {
    this.fontOffset = item;
    PreferenceUtils.saveModifyFont(item);
  })

item 为空字符串表示系统默认字体,不为空是自定义字体名称。

第六步:用 fontFamily 属性应用字体

// Example Text Content
Column() {
  Text($r('app.string.preview_text'))
    .fontFamily(this.fontOffset)
}
.width('100%')
.padding(12)
.margin({ top: 35 })
.backgroundColor('#FFF')
.borderRadius(16)

fontFamily 传入 fontOffset 变量,显示对应字体样式。

三、从自定义字体恢复为系统字体

场景咋回事

字体设置里点击系统默认字体,页面字体变为系统默认。退出重新进入,仍显示系统默认字体。

实现原理

系统默认字体是无衬线字体 HarmonyOS Sans。当 fontFamily 显式设为空字符串时,效果和不设置 fontFamily 一致,回退到默认字体。字体样式、字重等其他属性正常应用。

开发步骤

跟使用自定义字体流程一样,关键点:

MenuItem 列表里第一个选项是空字符串(表示系统默认):

Menu() {
  ForEach(this.menuItemArr, (item: string) => {
    MenuItem({
      content: item === '' ? $r('app.string.system_default') : item,
      endIcon: this.fontOffset === item ? $r('app.media.checkmark') : ''
    })
      .onChange(() => {
        this.fontOffset = item;
        PreferenceUtils.saveModifyFont(item);
      })
  }, (item: string) => item)
}

点击系统默认时,fontOffset 设成空字符串,fontFamily 也变成空字符串,字体恢复系统默认。

四、字体大小跟随系统设置

场景咋回事

设置页面点击打开 Toggle 开关,页面字体大小跟随系统设置变化。此时自定义字体大小和字重的 Slider 被禁用,无法滑动或点击。

实现原理

app.json5 配置文件里,configuration 标签标识字体大小是否跟随系统。当 fontSizeScale 属性设成 followSystem,且 fontSize 属性用 fp 为单位时,改变系统字体大小缩放比例,应用字体也会变化。

开发步骤

第一步:定义配置文件

AppScope/resources/base/profile 下创建 configuration.json

{
  "configuration": {
    "fontSizeScale": "followSystem",
    "fontSizeMaxScale": "2"
  }
}

属性说明:

  • fontSizeScale: followSystem(跟随系统)或 nonFollowSystem(不跟随系统),默认 nonFollowSystem
  • fontSizeMaxScale: 应用字体相对系统字体的最大比例

第二步:app.json5 引用配置

{
  "app": {
    "bundleName": "com.example.textdisplayfont",
    "vendor": "example",
    "versionCode": 1000000,
    "versionName": "1.0.0",
    "icon": "$media:layered_image",
    "label": "$string:app_name",
    "configuration": "$profile:configuration"
  }
}

configuration 字段引用 profile 目录下的配置文件。

第三步:Toggle 控制跟随状态

Toggle({ type: ToggleType.Switch, isOn: this.toggleState })
  .onChange((isOn: boolean) => {
    this.toggleState = isOn;
  })

toggleState 为 true 表示跟随系统。

第四步:根据 toggleState 控制 Slider 可交互

Slider({
  min: -4,
  max: 4,
  value: this.fontSizeOffset,
  style: SliderStyle.InSet
})
  .width('90%')
  .margin({ top: 12 })
  .enabled(!this.toggleState)

enabled(!this.toggleState)

  • toggleState 为 true(跟随系统),Slider 禁用
  • toggleState 为 false(不跟随),Slider 可交互

配置完成后,字体大小和字重随系统设置变化。

五、字体大小不跟随系统设置

场景咋回事

设置页关闭 Toggle 开关,通过 Slider 调整页面字体大小。在系统设置调整字体大小,页面字体不变。

实现原理

app.json5 配置了字体跟随系统设置后,当 fontSize 用屏幕物理像素单位 px 时,页面字体大小不再受系统设置影响。

开发步骤

第一步:fp 转 px 工具函数

getDefaultDisplaySync() 获取屏幕实例,取出 densityDPI 物理像素密度。把 fp 数值转成 px:

// Convert fp to px
export function fp2pxUtil(fp: number): string {
  const pxStr: string = 'px';
  let pxVal: number = 0;
  let displayClass: display.Display | null = null;
  try {
    displayClass = display.getDefaultDisplaySync();
    pxVal = fp * (displayClass.densityDPI / 160);
  } catch (err) {
    let error = err as BusinessError;
    hilog.error(0x0000, TAG, `get densityDPI failed. code: ${error.code}, message:${err.message}`);
  }
  return pxVal + pxStr;
}

公式:pxVal = fp * (densityDPI / 160)

  • densityDPI 是设备物理像素密度
  • 160 是基准 DPI

第二步:根据 toggleState 切换字体单位

Text($r('app.string.setting'))
  .width('100%')
  .fontWeight(700)
  .fontSize(this.toggleState ? 26 : fp2pxUtil(26))
  • toggleState 为 true:用 number 类型,单位 fp,字体跟随系统
  • toggleState 为 false:调用 fp2pxUtil() 转 px,字体不跟随系统

六、获取系统缩放比例系数

有时需要获取系统字体缩放比例来手动调整字体大小。

第一步:app.json5 不跟随系统

不配置 configuration 标签,或者 fontSizeScale 设成 nonFollowSystem

第二步:监听系统环境变化

onConfigurationUpdated() 获取缩放比例变化,通过 ApplicationContext.on('environment') 监听:

// System environment change information
let envCallback: EnvironmentCallback = {
  onConfigurationUpdated(config) {
    envFont.fontSizeScale = config.fontSizeScale; // Font size scaling ratio
    envFont.fontWeightScale = config.fontWeightScale; // Font thickness scaling ratio
  },
  onMemoryLevel(level) {
    hilog.info(DOMAIN, TAG, `onMemoryLevel level: ${level}`);
  }
}
let appContext = this.context.getApplicationContext();
// Register to monitor changes in the system environment
callbackId = appContext.on('environment', envCallback);

回调里能拿到:

  • config.fontSizeScale:字体大小缩放比例
  • config.fontWeightScale:字体粗细缩放比例

第三步:手动调整字体大小

基础字体大小 * 缩放系数,传给 fontSize() 属性。

比如基础 20fp,缩放比例 1.5,实际字体大小 = 20 * 1.5 = 30fp。

七、踩坑总结

坑一:字体文件放错位置

字体文件(TTF/OTF)要放在 src/main/resources/rawfile 目录。用 $rawfile('xxx.ttf') 引用。

别放 basedark 目录,那些是颜色、字符串资源目录。

坑二:registerFont 要在页面加载前调用

registerFont() 要在用字体前调用。一般放 aboutToAppear() 里。不然 Text 组件用 fontFamily 找不到注册的字体,显示不出来。

坑三:fontFamily 空字符串是系统默认

fontFamily 设成空字符串,等于不设置,回退到系统默认字体 HarmonyOS Sans。

别以为空字符串会报错或显示异常,它就是默认值。

坎四:fp 和 px 单位区别

  • fp(font pixel):跟随系统字体缩放
  • px(物理像素):不跟随系统字体缩放

要字体跟随系统就用 fp,不跟随就用 px。

坑五:fontSizeMaxScale 别设太大

fontSizeMaxScale 是应用字体相对系统字体的最大比例。设太大(比如 3 或 4),系统字体调很大时,应用字体可能超出屏幕范围,显示异常。建议设 2。

八、整体流程总结

使用自定义字体:

1. 创建首选项工具类(存取字体数据)
2. EntryAbility 里获取首选项实例
3. 定义 registerFont 方法注册字体
4. 页面加载时注册字体,读取首选项
5. MenuItem 点击时保存字体选择
6. Text 组件 fontFamily 应用字体

字体跟随系统:

1. 定义 configuration.json(fontSizeScale: followSystem)
2. app.json5 引用配置文件
3. Toggle 控制是否跟随
4. Slider 根据状态禁用/启用

字体不跟随系统:

1. fp2px 工具函数转换单位
2. fontSize 根据 toggleState 切换单位

关键点:

  • registerFont():注册自定义字体
  • fontFamily:空字符串恢复系统默认
  • fontSizeScale: followSystem:跟随系统设置
  • fontSize 用 px:不跟随系统设置
Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐