订单卡片上通常有编号、状态、时间、说明,还有一个看起来很明显的操作按钮。视觉用户扫一眼就能理解,但在读屏模式下,这些文字可能变成一串互不关联的焦点:先念编号,再念“待提交”,然后念一遍检查数量,最后才抵达按钮。一个业务对象被拆成许多碎片,页面能点,却不一定好用。

本篇不把“上架前自检”写成一次已经通过的官方审核。使用 AccessCheck 作为应用内部可访问性检查示例,围绕 QC-016 核验单分析 ArkUI 的 accessibilityGroup(true)、accessibilityText() 与装饰组件的焦点排除。演示设定为“检查项 6/6、语义修复 2 项、状态待提交”,这些数字是人为编排的验收样本,不是商店审核结论。

一、界面可见,不等于读屏路径正确

视觉排版关心对齐和层次,读屏顺序关心用户是否能用少量焦点理解业务对象。对于一张订单核验卡片,编号 QC-016、状态“待提交”和“六项检查已完成”属于同一个实体的信息。如果三个文本都独立获得焦点,信息会被拆散;如果把整页粗暴设成一个无障碍组,又会吞掉需要单独操作的按钮。

华为开发者文档把几类情况区分得很清楚:有文本的组件不应无故重复定义读屏内容;多组件表达同一对象时可以聚合;纯装饰组件应避免占用焦点;图标型操作按钮需要清楚标明动作,而不是仅让读屏服务念出图标。基于这几条原则,本例只把核验单的信息区域合并,确认按钮留在外面,继续作为独立可操作对象。

1. 先拆分“语义对象”和“操作入口”

AccessCheck 的工程目录很简单:pages/ReviewPage.ets 展示页面,components/ReviewCard.ets 负责核验单摘要,model/ReviewRule.ets 储存六项静态检查数据。页面不访问真实订单,也不自动读取系统读屏开关,因此能把讨论范围限制在 ArkUI 的可访问性语义声明上。

这里还有一个设计判断:状态不能只靠颜色传达。橙色标签写了“待提交”,读屏文本里也应该直接写“待提交”,否则对某些用户来说,最关键的下一步状态被抹掉了。即使颜色对比和字重都很理想,也不能代替这行语义描述。

二、卡片聚合不应覆盖按钮

1. 将一张核验单读成一个对象

第一段代码解决卡片信息被重复逐项朗读的问题。@Prop 接收业务字段,保证可视文本和语义文本由同一份状态生成;accessibilityGroup(true) 仅放在摘要 Row 上,而不是整个页面容器上。按钮在父页面创建,拥有自己的焦点机会。

// components/ReviewCard.ets
@Component
export struct ReviewCard {
  @Prop id: string = 'QC-016';
  @Prop status: string = '待提交';
  @Prop checkCount: number = 6;

  build() {
    Row({ space: 14 }) {
      Column({ space: 6 }) {
        Text(`核验单 ${this.id}`).fontSize(20)
        Text(this.status).fontColor('#B85632')
        Text(`检查项 ${this.checkCount}/${this.checkCount}`)
      }.alignItems(HorizontalAlign.Start)
    }
    .padding(16)
    .width('100%')
    .backgroundColor('#FFF7F1')
    .borderRadius(16)
    .accessibilityGroup(true)
    .accessibilityText(`核验单 ${this.id},${this.status},` +
      `${this.checkCount}项检查已完成`)
  }
}

这一段是“信息组”,没有把交互按钮塞进群组中。accessibilityText() 不是 UI 文案的第二份手工副本,而是根据 id、status 和数量计算出来的短句。哪怕将来核验单状态改成“已核验”,页面文本和读屏描述也会同步变化。需要注意,真正的检查可能只有部分完成,此时“完成数”和“总数”必须使用两个字段;这里写成同一个数字仅因为演示数据固定为 6/6。

关于 .accessibilityLevel('no'),它适合不承担信息或动作的分割线、纯背景图案等装饰元素,不应直接加到这个已经需要读屏的摘要组上,否则可能让整个摘要失去可感知性。工程检查时,应刻意验证父级与子级的可访问性关系,而不是看到“组”字就认为所有问题都解决了。

三、状态变化是第二个容易漏掉的焦点

读屏语义如果只在页面初次加载时写一次,就可能与按钮动作脱节。演示页面初始处于“待提交”,动作叫“确认核验”;操作后切为“已核验”,动作应变成“撤销核验”。视觉文本与读屏名称必须共享同一布尔状态,不允许按钮仍显示“确认”,读屏却报“撤销”。

下面这段处理父页面的状态切换。按钮放在卡片语义组之外;@State reviewed 驱动卡片状态与动作标签,console.info 仅输出演示 ID 和动作,不记录用户信息。代码解决的是内部核验交互,并没有调用应用市场审核 API。

// pages/ReviewPage.ets:摘取主要可交互页面
import { ReviewCard } from '../components/ReviewCard';

@Entry
@Component
struct ReviewPage {
  @State reviewed: boolean = false;
  private readonly id: string = 'QC-016';
  private readonly checkCount: number = 6;

  private switchReview(): void {
    this.reviewed = !this.reviewed;
    const label = this.reviewed ? '撤销核验' : '确认核验';
    console.info(`AccessCheck ${this.id} actionLabel=${label}`);
  }

  build() {
    Column({ space: 20 }) {
      Text('发布前无障碍核验').fontSize(26)
      ReviewCard({ id: this.id,
        status: this.reviewed ? '已核验' : '待提交',
        checkCount: this.checkCount })
      Button(this.reviewed ? '撤销核验' : '确认核验')
        .accessibilityText(this.reviewed ? '撤销核验' : '确认核验')
        .enabled(this.checkCount === 6)
        .onClick(() => this.switchReview())
      Text('语义修复 2 项;图片与页面均为演示素材')
        .fontColor(Color.Gray)
    }.padding(20).width('100%')
  }
}

这段结构刻意没有使用“仅通过设置按钮颜色表示不可用”的设计。.enabled() 是可操作状态,读屏服务也有机会获知控件状态;如果业务上暂时不允许提交,页面还应提供明确原因,例如“还有未完成检查项”。示例中的 checkCount===6 只是固定样本判断,生产业务不能把“总数恰好为六”当作通过条件。

图中的左侧工程树、中央 ArkTS、右侧预览和底部 HiLog 都是演示绘制。右侧展示初始的 QC-016 / 待提交 / 6/6;日志栏为说明事件序列,可能包含稍后点击状态改变的记录,不代表同一时刻的真机采样。代码位置以本文为准,尤其不能把 .accessibilityLevel('no') 加在需要朗读的整个摘要组上。

1. 将检查项设计成能复验的静态规则

页面自己显示“6/6”不等于六个检查真的已经人工验收。更稳妥的做法,是让规则表明确列出需要核实的对象、操作、预期朗读内容和结论。下面是第三段示例代码,把“六项已勾选”和“两项语义修复”变成可计算的演示数字,便于之后替换为人工验收记录。

// model/ReviewRule.ets:人工录入的自检结果示例
export interface ReviewRule {
  key: string;
  passed: boolean;
  semanticFix: boolean;
}
export const demoRules: ReviewRule[] = [
  { key: '卡片语义分组', passed: true, semanticFix: true },
  { key: '状态文字同步', passed: true, semanticFix: true },
  { key: '按钮动作可读', passed: true, semanticFix: false },
  { key: '装饰焦点排除', passed: true, semanticFix: false },
  { key: '禁用原因说明', passed: true, semanticFix: false },
  { key: '返回路径检查', passed: true, semanticFix: false }
];
export const passedCount: number = demoRules.filter(
  (item: ReviewRule) => item.passed).length;
export const semanticFixes: number = demoRules.filter(
  (item: ReviewRule) => item.semanticFix).length;

它不是自动化无障碍检测器。passed=true 代表示例作者手动给出的一条输入,不能冒充系统扫描的结果。尤其“返回路径检查”“禁用原因说明”仍需要真实设备上的手势、焦点与朗读核对,静态规则表无法代替读屏测试。生产版本还应保存测试设备、系统版本和验收者,必要时记录问题复现步骤;这些信息在本次示例中全部留空,不作虚构。

四、把截图里的字段变成一份检查协议

手机画面使用统一数据:项目 AccessCheck,核验单 QC-016,状态“待提交”,检查项 6/6,语义修复 2 项,按钮“确认核验”。其中“读屏语义已合并”表达的是设计层面的属性配置,不是实际开启屏幕朗读后已经听到的录音结果。图中时间和电量属于示意状态栏,不能据此推断测试环境。

如果把这张画面作为开发任务验收附件,应把检查分成三个层次。第一层是代码审阅:同一对象使用语义分组,图标按钮有读屏名称,纯装饰组件不占焦点。第二层是交互核对:点击后状态与动作名称同步,取消后能恢复,页面返回不会留下错误的“已核验”。第三层是真机核对:打开屏幕朗读,逐个移动焦点,确认读屏顺序、是否重复、按钮是否能执行以及状态是否能得到恰当提示。

还有一个时序细节:某些状态变化不仅要更新语义标签,还需要主动告知当前正在使用读屏的用户。华为指南介绍了主动播报与无障碍焦点调整的场景,但该类能力需要结合实际页面窗口、操作触发点和系统行为验证。不能为了演示,把“已核验”无条件反复播报,造成额外干扰。只有当信息确实重要且无法从自然焦点顺序获得时,才考虑主动通知。

五、发布前清单不能替代平台审核结论

这套方案适合按钮密集、状态随业务变化的订单页、核验页和提交流程:它把“视觉上一张卡片”映射成“读屏上的一个业务对象”,再把需要操作的按钮拆出来。真正的价值不是多加两个修饰属性,而是将状态源、可视标签、朗读信息、事件反馈放回同一条业务链路。

也需要把边界说清楚:可访问性体验质量与应用市场的完整审核要求并不等价。本文不声称平台必须采用这六项标准,也不声称配置 accessibilityGroup 就能通过上架审核。开发者仍要根据目标设备、版本与最新发布要求核对其它合规和技术项目。当前代码未经 DevEco Studio 实际构建或系统读屏测试,交付图片只是便于讨论组件关系的演示界面。

参考资料:

  • 华为开发者文档《适配屏幕朗读》:https://developer.huawei.com/consumer/en/doc/harmonyos-guides-V14/screen-reading-adapt-guide-V14
  • 华为开发者文档《Accessibility Kit 简介》:https://developer.huawei.com/consumer/en/doc/harmonyos-guides-V5/accessibilitykit-overview-V5

配图为本篇独立生成的封面、DevEco 风格示意图及无边框手机页面。所有核验数据、状态和日志均是示例,不代表真实 AppGallery Connect 审核结果。

Logo

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

更多推荐