一个待办事项在视觉上看起来非常简单:左侧一行标题,右侧一个“完成”按钮,下面再放一行说明。可把屏幕阅读功能打开以后,用户听到的可能是“按钮、按钮、文本”,却听不出来哪个按钮对应哪条任务。再把系统字体调大,标题挤成三行,按钮被挤出屏幕;页面原本展示得越紧凑,问题反而越容易暴露。设计一个看起来整洁的列表,跟设计一个可以被不同用户稳定操作的列表,是两项需要分别核查的工作。

这次选择一个完全独立的 HarmonyOS 待办清单页面,不需要前面任何连载项目,也不依赖三方库。评审目标包括三件事:每条内容能被完整理解、每个操作能被明确识别、文字变大以后交互仍然可达。代码以 ArkUI 的普通组件属性为主,避免把系统 Accessibility Kit 服务侧接口误当成页面必须调用的权限能力。

HarmonyOS 无障碍待办清单技术封面

从视觉信息结构反推朗读结构

先明确产品中的最小语义单位。这里每条待办包含稳定 id、title、detail 和 completed 四项,用户可执行的动作是“标记完成”或“撤销完成”。标题和描述是静态内容,右侧按钮是独立交互对象。视觉上可以用卡片把二者放在一起,但无障碍焦点不应把整个卡片当成一个大按钮,因为这样会让用户无法准确区分“正在朗读信息”和“准备执行动作”。

推荐的阅读路径是:先听到任务标题及说明,然后移动焦点到与该任务绑定的操作按钮。按钮的朗读内容应包含任务名和动作含义,而不是只说“完成”。例如“标记完成:检查备份文件”,要比相邻多个按钮都叫“完成”更容易操作。若按钮执行后状态改变,文案要跟着切换成“撤销完成:检查备份文件”,避免视觉已经更新、辅助技术仍读出旧动作。必要时再补充 accessibilityDescription 告知操作后果,而不是把每个状态细节都挤进屏幕上的按钮标题。

对纯装饰性的分隔线和重复图形,原则相反:不承载信息,就不要制造一个多余焦点。官方屏幕朗读适配指南说明,accessibilityGroup(true) 可以把静态子内容归为一个整体,accessibilityLevel('no') 可以让无需被识别的装饰对象不进入朗读导航。但含独立按钮的整个行容器不能盲目设置为一个朗读组:组件被合并后,内部控件可能不再分别获得无障碍焦点。

一条待办卡片的静态语义区和独立操作区

先让一个列表项读得准确,再让列表跑起来

下面提供一个可以直接放在 Stage 模型 entry 模块 src/main/ets/pages/Index.ets 中的简化页面。它只依赖 ArkUI 标准组件,不访问网络、相册、位置或其他受限资源,因此无需额外声明危险权限。列表状态在页面内保存;如果产品需要跨启动持久化,可以另行接入存储服务,但那不属于无障碍属性的职责范围。

interface TodoItem {
  id: string;
  title: string;
  detail: string;
  completed: boolean;
}

@Entry
@Component
struct Index {
  @State items: TodoItem[] = [
    { id: 'backup', title: '检查备份文件',
      detail: '确认昨天的归档文件可以正常打开', completed: false },
    { id: 'review', title: '复核上线清单',
      detail: '核对依赖版本、权限与发布说明', completed: true },
    { id: 'notice', title: '发送项目提醒',
      detail: '告知协作成员本周的交付安排', completed: false }
  ];

  private toggle(id: string): void {
    this.items = this.items.map((item: TodoItem) => {
      if (item.id !== id) return item;
      return { id: item.id, title: item.title,
        detail: item.detail, completed: !item.completed };
    });
  }

  build() {
    Column({ space: 12 }) {
      Text('今日待办')
        .fontSize(26)
        .fontWeight(FontWeight.Bold)
        .width('100%')

      Scroll() {
        Column({ space: 12 }) {
          ForEach(this.items, (item: TodoItem) => {
            Row({ space: 12 }) {
              Column({ space: 6 }) {
                Text(item.title)
                  .fontSize(18)
                  .fontWeight(FontWeight.Medium)
                  .width('100%')
                Text(item.detail)
                  .fontSize(15)
                  .fontColor('#4A5568')
                  .width('100%')
              }
              .layoutWeight(1)
              .alignItems(HorizontalAlign.Start)
              .accessibilityGroup(true)
              .accessibilityText(item.title + '。' + item.detail)

              Button(item.completed ? '撤销' : '完成')
                .onClick(() => this.toggle(item.id))
                .accessibilityText(
                  (item.completed ? '撤销完成:' : '标记完成:') + item.title
                )
                .accessibilityDescription(
                  item.completed ? '将这项任务改为未完成' : '将这项任务改为已完成'
                )
            }
            .width('100%')
            .padding(14)
            .borderRadius(12)
            .backgroundColor('#F5F8FC')
          }, (item: TodoItem) => item.id)
        }
        .width('100%')
      }
      .layoutWeight(1)
    }
    .padding(18)
    .width('100%')
    .height('100%')
  }
}

ForEach 使用稳定的业务 id 作为键,而不是数组下标。这样当未来增加排序和筛选时,条目身份不会因索引变化而混淆。toggle() 创建新数组并重新赋值,方便 ArkUI 感知列表状态变化。此处没有把“完成”状态只画成绿色对勾;操作文案本身会根据状态同步变化,即使用户看不到颜色,也能知道下一次点击的含义。

代码最值得注意的不是某个复杂 API,而是分组范围。只把左侧标题和说明放进 Column 的朗读组;右侧 Button 保留独立操作焦点。为了避免同一个信息被重复播报,组的 accessibilityText 提供一次完整的摘要,按钮则用带任务名称的动词短句表达动作。实际屏幕阅读顺序受布局和系统行为影响,仍需在目标设备上核查,不应仅凭代码顺序推断真实朗读效果。

大字体不是把 16 改成 20 就算适配

设计评审常把文字放大当成“字号提升”,但真正需要关注的是布局约束。假设任务说明由一行扩为三行,左侧 Column 是否还能自然增高?右侧操作按钮是否被压缩得无法点中?整个页面是否具备纵向滚动?这些问题不能靠给每个 Text 一个固定高度解决。相反,固定高度和过度使用省略号往往会隐藏重要信息。

本示例使用 Scroll 承载纵向内容,列表卡片不固定高度,文本区域通过 .layoutWeight(1) 占据可用宽度。这是保持内容伸展能力的基本结构,而非承诺所有极端字体倍率都能自动适配。尤其在非常窄的窗口中,标题和按钮可能相互争夺宽度,此时应考虑把操作按钮移到卡片下一行。不要仅依靠自动缩小字号,因为“内容完整”和“操作易达”通常比保持单行排版更重要。

可以用一个独立的布局决策函数把分支写清楚,再让组件按结果安排横向或纵向容器。这是项目自身的纯函数,不涉及未开放的系统设备识别接口:

export type CardArrangement = 'horizontal' | 'vertical';

export function chooseArrangement(
  availableWidthVp: number,
  requiresLargeText: boolean
): CardArrangement {
  if (requiresLargeText || availableWidthVp < 360) {
    return 'vertical';
  }
  return 'horizontal';
}

这里的 360vp 是当前示例的设计阈值,不是 HarmonyOS 平台规定的通用断点。requiresLargeText 也应由应用已验证的字体适配策略确定,不能随意根据手机型号猜测。将其接入实际布局时,可用父容器宽度或布局约束选择 Row 与 Column,并在大字体模式下让“完成”按钮单独占据下一行。更重要的是重新检查焦点走向:排列方向变了之后,语义阅读仍应先接收任务信息,再到操作按钮。

标准字号与大字号卡片布局及焦点路径对比

用可核对的路径完成无障碍验收

适配结果不该只由设计师看一眼截图决定。首先使用系统屏幕阅读功能从页面标题进入第一条任务,按正常手势依次移动焦点,记录是否完整读出标题、说明和动作。然后点击第二条任务的“撤销”,继续移动焦点,确认动作文案反映变化。接下来把系统文本显示调大,检查三条卡片是否能完整滚动、按钮是否可见且可操作,尤其注意长说明和窄窗口。最后使用键盘或其他可用输入方式确认焦点导航顺序合理,不能出现不可见但仍被选中的控件。

这套验收至少要覆盖四类问题。内容问题:是否漏读了状态、单位或行动对象;操作问题:同名按钮能否区分;布局问题:文字放大后是否遮挡或裁切;焦点问题:装饰对象有没有抢占焦点、可交互对象有没有被错误合并。每个问题应记录操作步骤、预期朗读内容、实际观察与修复位置。若发现用户需要重复听到相同说明才能找到按钮,应优先优化语义组织,而不是把朗读文本写得越来越长。

无障碍不是给页面最后加一个属性就结束。对这个待办清单来说,它首先是一项信息架构设计:哪些元素有意义、哪些是操作、哪些应被合并,决定了信息能不能有效传达;其次才是屏幕宽度、字号和焦点路径的工程落地。把静态语义与交互动作分开,让内容可以自然换行、让状态变化具备可朗读文案,再在真实设备上走完操作路径,比只看正常字号下的漂亮截图更接近可交付的质量标准。

官方资料:应用适配屏幕朗读、焦点控制、Accessibility Kit 概述。

Logo

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

更多推荐