【HarmonyOS 7新能力|043】可变字体工程封装:把接入逻辑放进可维护的分层结构

【HarmonyOS 7新能力|043】可变字体工程封装:把接入逻辑放进可维护的分层结构

可变字体可以在一份字体资源中提供连续字重、宽度或光学尺寸变化,让标题、正文和交互状态获得更细腻的视觉层级。但如果页面直接填写轴值,多语言字形缺失、无障碍缩放、主题切换和设备差异会迅速把样式逻辑打散。工程化目标不是让每个组件"会调字体",而是建立语义样式到字体能力的稳定映射。

说明:文中的 VariableFontPort、轴值配置和代码均为教学抽象,并非 HarmonyOS SDK 的真实接口。可用字体格式、轴能力、注册方式与 ArkUI 属性请以当前官方文档和目标 SDK 为准。

1. 先用语义角色定义排版

页面应该声明标题、正文、标签、数字强调等角色,而不是到处复制字号、字重和字体名。语义角色保持产品层级一致,字体实现可以随语言和设备替换。

ts 复制代码
export type TextRole = 'display' | 'title' | 'body' | 'label' | 'number'

export interface TypographyRequest {
  role: TextRole
  locale: string
  scale: number
  emphasis: 'normal' | 'strong'
  availableWidth: number
}

scale 来自可访问性设置,不能为了防止溢出而被页面静默忽略。

2. 四层结构隔离字体细节

业务页面使用语义令牌;排版策略层计算字号、行高和变体意图;字体能力适配层完成注册、轴探测和样式映射;字体与样式仓库维护资源、元数据、语言覆盖与缓存。

ts 复制代码
export interface VariableFontPort {
  inspect(fontId: string): Promise<FontCapability>
  resolve(request: FontResolveRequest): Promise<ResolvedFont>
}

export interface TypographyRepository {
  getToken(role: TextRole, locale: string): Promise<TypographyToken>
  getFallbackChain(locale: string): Promise<readonly string[]>
}

页面不持有字体文件路径,仓库不依赖具体组件,平台变化留在适配器。

3. 注册后先探测能力

同名字体在不同版本中可能包含不同轴。使用前读取支持轴、范围和默认值,生成能力快照;不存在的轴不应强行设置。

ts 复制代码
export interface VariationAxis {
  tag: string
  min: number
  max: number
  defaultValue: number
}

export interface FontCapability {
  fontId: string
  axes: readonly VariationAxis[]
  supportedLocales: readonly string[]
  version: string
}

能力快照按字体版本缓存,资源升级后失效。字体解析失败时立即进入回退链。

4. 轴值计算必须归一化

设计令牌可以表达 0 到 1 的视觉强度,适配层再映射到实际轴范围。这样设计系统不依赖某一字体的原始数值。

ts 复制代码
export function mapAxis(normalized: number, axis: VariationAxis): number {
  const value = Math.max(0, Math.min(1, normalized))
  return axis.min + (axis.max - axis.min) * value
}

export function axisOrDefault(capability: FontCapability, tag: string, value: number): number {
  const axis = capability.axes.find(item => item.tag === tag)
  return axis ? mapAxis(value, axis) : 0
}

实际轴标签和语义必须来自字体元数据,不自行假设所有字体都支持相同能力。

5. 可变字体排版流程

流程依次完成字体注册、能力探测、语言识别、轴值计算、样式应用和视觉校验。字体缺失、轴不支持或文本溢出进入回退路径,然后重新计算布局,而不是保留错误样式。

ts 复制代码
export type FontResolution =
  | { status: 'resolved'; font: ResolvedFont }
  | { status: 'fallback'; font: ResolvedFont; reason: string }
  | { status: 'system'; reason: string }

UI 只消费最终结果,诊断信息则用于定位为何发生回退。

6. 多语言按覆盖范围选择字体

一段文本可能同时包含中文、拉丁字母、数字和符号。语言标签只是第一步,还要确认字体对实际字符有覆盖。缺失字形时按脚本或字符簇使用回退,避免整段突然变成不一致字体。

ts 复制代码
export interface ScriptRun {
  text: string
  script: 'Hans' | 'Hant' | 'Latin' | 'Kana' | 'Hangul' | 'Other'
  direction: 'ltr' | 'rtl'
}

function selectFamily(run: ScriptRun, chain: readonly FontDescriptor[]): FontDescriptor {
  return chain.find(font => font.supports(run.script, run.text)) ?? systemFallback(run.script)
}

复杂脚本的分段和塑形应交给平台能力,不用简单字符循环替代正式排版引擎。

7. 字重变化服务于层级而非装饰

连续字重最适合表达标题层级、选中状态或轻微强调。动画变化需要限制范围和时长,不能让正文持续呼吸式变化影响阅读。

ts 复制代码
export interface WeightIntent {
  base: number
  emphasized: number
  pressed: number
  disabled: number
}

function resolveWeight(intent: WeightIntent, state: ComponentState): number {
  if (state.disabled) return intent.disabled
  if (state.pressed) return intent.pressed
  if (state.selected) return intent.emphasized
  return intent.base
}

状态令牌集中维护,所有同类组件得到一致反馈。

8. 无障碍缩放需要重新布局

字体放大不仅改变字号,还影响行高、换行和容器高度。排版策略先应用用户缩放,再根据可用宽度决定行数;重要文本不能用缩小字体来塞进固定框。

ts 复制代码
export interface TextLayoutPolicy {
  minLines: number
  maxLines?: number
  overflow: 'wrap' | 'ellipsis'
  allowContainerGrowth: boolean
}

export function scaledSize(base: number, userScale: number): number {
  return base * Math.max(1, userScale)
}

按钮、列表行和弹窗在大字体下都要真机检查,操作区域必须仍然可达。

9. 响应式排版不是等比缩放

手机、平板和 2in1 的阅读距离与列宽不同。宽屏时正文不应无限拉长,而应限制阅读列宽、调整留白;小窗则优先换行和减少装饰,不牺牲正文可读性。

ts 复制代码
export interface LayoutTypographyContext {
  widthClass: 'compact' | 'medium' | 'expanded'
  windowWidth: number
  userScale: number
  locale: string
}

策略根据宽度等级选择令牌集,不在组件中写多个零散断点。

10. 缓存以能力和样式键为边界

字体解析和样式构造可以缓存,但键必须包含字体版本、语言、主题、缩放和语义角色。否则主题或语言切换后可能复用陈旧结果。

ts 复制代码
export function typographyCacheKey(input: FontResolveRequest): string {
  return [input.fontVersion, input.locale, input.theme, input.role,
    input.scale, input.emphasis].join('|')
}

缓存只保存解析结果,不长期复制字体大对象。内存告警时允许清理并重新解析。

11. 失败回退必须保持内容可读

字体文件损坏、注册失败或轴设置不支持时,优先回退到覆盖当前语言的系统字体。轴值越界则裁剪到范围,文本仍溢出时允许容器增长、换行或省略非关键文本。

ts 复制代码
export interface TypographyFailure {
  code: 'FONT_MISSING' | 'AXIS_UNSUPPORTED' | 'GLYPH_MISSING' | 'OVERFLOW'
  fontId?: string
  locale: string
  role: TextRole
}

日志不记录完整用户文本,只记录角色、语言、字体版本和错误码。

12. 验收清单与总结

验收覆盖简体中文、繁体中文、英文、日文、韩文及混排,检查缺字、方向、标点、数字和换行;覆盖普通与大字体、深浅主题、手机小窗、平板和 2in1;测试字体缺失、轴不支持与缓存失效。视觉检查关注正文对比度、行长、行高和交互文本是否截断。

可变字体工程化的重点,是把字体轴从页面参数提升为设计系统能力。业务使用语义角色,策略层处理语言和缩放,适配层尊重真实字体轴,仓库提供回退与缓存。即使特定字体或轴不可用,内容仍能正确、清晰地呈现,才是可靠的多语言排版体系。

相关推荐
HarmonyOS_SDK2 小时前
HarmonyOS智慧多窗,让应用在任意窗口都“恰到好处”
harmonyos
传奇开心果编程5 小时前
【ArkUI 练中学】第14课:应用性能优化实战
学习·ui·华为·harmonyos
用户593096009785 小时前
Flutter 鸿蒙化实战:flutter_tts 适配 OpenHarmony,文本转语音
harmonyos
威哥爱编程7 小时前
HarmonyOS 互动卡片实战:摇一摇触发静态转动态,前景元素出框
华为·harmonyos·arkts
威哥爱编程8 小时前
HarmonyOS 7 闪控窗实战:标准悬浮窗、侧边栏暂存与闪控球一键切换
华为·harmonyos·arkts
威哥爱编程8 小时前
HarmonyOS 7 图像超分实战:端侧 4 倍高清放大,数据不出设备
华为·harmonyos·arkts
威哥爱编程8 小时前
HarmonyOS 6.1 智能窥屏防护实战:实时感知窥屏风险,敏感页自动防护
华为·harmonyos·arkts
威哥爱编程8 小时前
HarmonyOS 7 文搜图实战:自然语言检索本地图片,全流程端侧闭环
华为·harmonyos·arkts
贾伟康8 小时前
【HarmonyOS 7新能力|041】弱网直播优化工程封装:把接入逻辑放进可维护的分层结构
性能优化·harmonyos·arkts·音视频开发·直播