一张界面看起来整洁,不等于每个人都能顺畅操作它。假设应用里有一张“费用报销单”卡片:左边是图标,中间是金额和审核状态,右边只有一个小小的勾选符号。开发人员手指轻点就能完成“归档”,而打开屏幕朗读后,用户听到的却可能是“图片”“三百二十八”“已审核”,最后根本不知道该在哪里执行归档。这个问题不在配色是否好看,而在界面有没有把对象的含义与动作告诉辅助服务。

ArkUI 无障碍语义和操作焦点技术封面

本篇围绕一个独立的报销记录卡片做完整的无障碍适配。它不依赖其他连载的票据数据库,也不讨论第三方朗读引擎。我们使用 ArkUI 已公开的 accessibilityText、accessibilityDescription、accessibilityGroup 和 accessibilityLevel 约定,为读屏用户提供简洁、有效的语义提示,再通过可复现的焦点遍历检查发现交互问题。代码使用普通 ArkTS 声明式组件,适用于 Stage 模型中的典型应用页面。

卡片上写了文字,为什么读屏还听不明白?

视觉阅读者看到卡片,会自然把标题、金额、日期、状态和图标看成一个业务对象。读屏服务面对的是组件及其语义信息:如果每个 Text、Image 都暴露独立焦点,用户需要多次滑动才知道它们共同组成一条记录。若那个代表“已审核”的绿色勾没有文字说明,颜色和图形并不能自动转换为“已经完成审核”的可听信息。

解决思路不是给每个元素叠加一句“大段介绍”,而是先判断页面中哪几个部分构成一个完整的对象。记录摘要在阅读层面可以合成一组;真正可点击的“归档”按钮则应保留单独的操作焦点。否则整个卡片被分组后,操作按钮也被吞进阅读组,用户虽然听懂内容,却可能找不到操作入口。

这正是 accessibilityGroup(true) 需要谨慎设置的原因。华为屏幕朗读适配文档说明:使用分组后,组内若干组件可作为一个整体被读取;但如果把独立的交互按钮也一起归入非交互阅读组,会损失可操作性。因此应该按语义聚合文字,按动作保留按钮,而不是简单对整张卡片调用一次分组属性。

卡片阅读组与归档按钮的焦点拆分

为什么一个普通按钮还要写 accessibilityText?

有可见文字的 Button('归档') 通常已经具有可理解的文本,并不需要机械重复。更值得做的是说明它归档哪一条记录、操作带来什么影响。特别在一页十几张卡片同时出现“归档”时,朗读“归档按钮”没有足够的上下文。可把无障碍名称设置为“归档差旅报销单”,再使用简短的描述补充“归档后移入已完成列表”。

accessibilityText 用于提供语义名称,accessibilityDescription 用于解释动作或特殊后果。两者不应互相抄写,否则屏幕朗读会把同一句话念两遍。对于纯装饰性的箭头、线条、阴影图标,可以用 accessibilityLevel('no') 让它不再占用独立的朗读焦点;如果整个容器及子孙节点都没有阅读价值,则再评估 no-hide-descendants。不要为了让读屏路径变短而隐藏错误提示、价格、日期或提交按钮。

下面先定义示例数据,不把页面状态和业务对象混在一起。归档动作只改当前页面的 archived 标志;实际项目中接入数据库或网络接口时,应在操作成功后再更新为“已归档”,失败则保留原状态并提供可读提示。界面示例包含金额、日期等模拟内容,但不作为真实报销记录。

// model/ExpenseItem.ets
export class ExpenseItem {
  id: string;
  title: string;
  amount: number;
  date: string;
  reviewed: boolean;
  archived: boolean;

  constructor(id: string, title: string, amount: number,
    date: string, reviewed: boolean, archived: boolean) {
    this.id = id;
    this.title = title;
    this.amount = amount;
    this.date = date;
    this.reviewed = reviewed;
    this.archived = archived;
  }
}

如何让读屏只读一次摘要,却能独立执行归档?

做法是把摘要区和动作区拆开。摘要区通过 accessibilityGroup(true) 暴露一个稳定语义对象;按钮自身保留点击行为,并带上当前记录的标题。下面的页面使用一个 @State 表达归档结果,并直接构建业务摘要。它故意不引入自绘图标库或复杂布局,便于看清分组边界在哪里。

// pages/AccessibilityExample.ets
@Entry
@Component
struct AccessibilityExample {
  @State private archived: boolean = false;
  private itemTitle: string = '差旅报销单';
  private amountText: string = '328元';
  private dateText: string = '2026年11月3日';

  build() {
    Column({ space: 18 }) {
      Text('报销记录').fontSize(26).fontWeight(FontWeight.Bold)
      Row({ space: 16 }) {
        Column({ space: 8 }) {
          Text(this.itemTitle).fontSize(20)
          Text(`金额:${this.amountText}`)
          Text(`日期:${this.dateText}`)
          Text(this.archived ? '状态:已归档' : '状态:已审核')
          Text('•').fontSize(20)
            .accessibilityLevel('no')
        }
        .alignItems(HorizontalAlign.Start)
        .accessibilityGroup(true)
        .accessibilityText(
          `${this.itemTitle},${this.amountText},${this.dateText},` +
          (this.archived ? '已归档' : '已审核'))

        Button(this.archived ? '已归档' : '归档')
          .enabled(!this.archived)
          .accessibilityText(
            this.archived ? `${this.itemTitle}已归档` : `归档${this.itemTitle}`)
          .accessibilityDescription('归档后移入已完成记录')
          .onClick(() => { this.archived = true; })
      }
      .padding(18)
      .backgroundColor('#F5F7FB')
      .borderRadius(16)
      .width('100%')
    }
    .padding(20)
    .width('100%')
    .height('100%')
  }
}

不要把上述 accessibilityGroup(true) 移到外层 Row:那里同时包含归档按钮,语义上是两种交互角色。阅读组不承担操作,按钮才承担操作。另一个容易忽略的细节是,按钮处于禁用态时用户的朗读路径和可操作性可能发生变化;因此“已归档”仍要在摘要文字中明确展示,不能只通过按钮颜色变化表达结果。辅助服务对禁用控件的处理与设备配置有关,业务信息不应依赖禁用按钮是否还被朗读。

如果卡片有多个并列按钮,如“查看详情”“撤销归档”,应保留各自的动作焦点,并让名称明确包含业务对象。将三个按钮合成一个“大区域点击”不一定提升无障碍体验,因为用户难以预测触发哪项操作。反之,把同一按钮做成“图标可点、文字也可点”两个重叠区域,又容易造成焦点重复。视觉点击面积、键盘焦点与屏幕朗读焦点各自有约束,设计时需要分别验证。

怎样区别焦点顺序问题与朗读文案问题?

两种问题看似接近,定位方法却不同。焦点顺序问题表现为用户从标题向下滑动时,先听到了页脚按钮,再听到页面中部的记录;这通常涉及组件布局、可见性和分组结构。朗读文案问题则是焦点顺序正常,但名称不包含对象、状态或操作后果,例如所有卡片按钮都只叫“确定”。排查时先画出页面上期望的焦点序列,再逐项记录实际读到的名称,避免边改文案边重排布局,最后无法判断改善来自哪一步。

以本例为例,合理的朗读路径可以是“报销记录”标题、接着“差旅报销单,328元,2026年11月3日,已审核”、再到“归档差旅报销单”。点击归档之后,摘要应变为“已归档”,视觉上的状态文字也应同步更新。用户若使用实体键盘,还需要能通过焦点导航到真实按钮;仅设置 accessibilityText 不会自动修复所有键盘交互问题。较完整的验收应分别覆盖触屏、读屏手势和键盘场景。

错误焦点顺序、重复朗读与修正后的对照

什么时候不该把一整行设置成无障碍分组?

如果一行里只有图标、标题和状态,它们共同表示一个不可交互的信息项,分组通常有价值。若一行同时放了可执行的开关、复选框、外链和删除按钮,就不能仅为了减少焦点数量把所有子控件隐藏。特别是 Toggle、Checkbox 等组件,本身有选中和启用状态;用一句人工拼接的文字替代它们的原生控件语义,容易让用户无法知道当前是开启还是关闭,也不利于辅助服务执行对应动作。

对于纯装饰性的图片,隐藏朗读焦点是合理的;对于带信息的图表、提示图和凭证缩略图,则应给出能解释其含义的替代文字。不要写“图片一”“图片二”,也不要把屏幕上所有文本一股脑复制到描述里。说明应回答“它是什么”“包含什么关键内容”“用户现在可以做什么”,而非描述色块、阴影、圆角的视觉细节。

动态反馈也是重要边界。状态改变后,屏幕朗读服务不一定因为普通 @State 更新就主动播报新内容。对于必须即时告知的提交结果,可以按官方 Accessibility Kit 文档评估主动播报事件;但不是所有文字更新都要强制朗读,否则频繁播报会打断用户当前操作。本示例只确保新状态能在下一次焦点到达时被正确读取,主动播报应根据具体业务严重程度与辅助服务行为另行设计。

无障碍信息密度与操作入口的工程评审卡

如何验收一张真正可用的无障碍卡片?

测试之前,在系统设置中启用屏幕朗读功能。然后从页面顶部按阅读顺序遍历所有对象,记录每个焦点的可见对象、实际朗读名称和能否触发预期动作。需要验证标题是否有明确意义,金额与状态是否完整,纯装饰元素是否不再产生无用焦点,归档按钮是否独立可达、文案是否包含对象名称,归档之后状态是否可重新感知。还应在放大字体、横屏和多栏布局条件下重复,以防视觉截断使按钮位置变化。

在验收过程中,不要凭模拟器里某个元素被高亮,就声称已经完成所有无障碍适配。不同辅助服务设置、字体缩放、输入方式和设备类型都会改变交互路径。要把误触率、焦点丢失、重复朗读和读不到关键操作作为问题记录,逐项修复,而不是仅检查属性名称有没有拼写错误。对于涉及资金、权限、隐私和删除操作的按钮,应特别明确后果,避免模糊的“继续”“确认”。

这套改造没有改变业务系统,却改变了用户与界面的沟通方式:信息按语义组合,动作按职责分离,动态结果有可感知的出口。做无障碍不是最后贴几段辅助文本,而是在组件结构设计阶段决定谁负责表达信息、谁负责执行动作。技术参考:华为屏幕朗读适配指南、华为 Accessibility Kit 概览。

Logo

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

更多推荐