工程 Demo:FoldA11y(FA-208)|方向:折叠屏大字体与无障碍可读性
说明:本文的宽度、状态、字体倍率及所有图片均是构造的设计回归案例;未完成 DevEco Studio 编译、系统字体缩放实测或读屏器真机验证。

看上去排好了,为什么仍然不好用

折叠屏页面常常有一个令人误判的阶段:开发者在展开态检查两列卡片,发现结构平衡、按钮位置一致,于是认为适配已经完成。但当用户把系统字体调大,或者应用处于窄窗口时,标题变成两行,按钮的朗读信息却依然只有一个“查看”,四张卡片都是相同提示。视觉上能找到入口的人还能猜图标,依靠屏幕朗读的用户则很难知道这个按钮将打开什么。这个问题不是换一个更大的断点就能解决,它同时涉及布局、稳定状态和交互语义。

FoldA11y 用四张固定操作卡片 A-01 至 A-04 做最小样本,任务号为 FA-208。我们规定窄窗口 360vp 时一列,展开后的示意窗口 760vp 时两列;在大字 1.6 倍的情境下,优先确保每条操作有明确名称和动作。“标签 4/4”仅表示 Demo 四个数据项都提供了可读文本,不等于屏幕朗读器真的正确播报了四次。这条区别会贯穿整个实现和调试记录。

把语义放到数据,不放在图标上

卡片不要只保存图片资源和一个路由名字。真正与交互相关的最小模型应该有稳定 ID、页面标题、可见操作文字、读屏解释和目标动作。本文没有涉及实际业务路由,因此按钮只将当前选择项写入 activeId,不凭空调用某个“折叠屏模式识别”接口。图片和颜色用于区分内容类别,却不能成为唯一的信息通道。即便图标换成占位色块,语义仍应完整。

interface ActionCard {
  id: string;
  title: string;
  action: string;
  hint: string;
}
const SAMPLE_CARDS: ActionCard[] = [
  { id: 'A-01', title: '文档管理', action: '查看', hint: '进入文档管理' },
  { id: 'A-02', title: '图片处理', action: '查看', hint: '进入图片处理' },
  { id: 'A-03', title: '语音助手', action: '查看', hint: '进入语音助手' },
  { id: 'A-04', title: '系统设置', action: '查看', hint: '进入系统设置' }
];

这里的 A-02 是图片处理,不因为窗口切换就变成新卡片。稳定业务键是保持选择项语义的基础;如果用下标当键,排序或过滤后第二区块可能对应另一条业务记录。对本例四项静态数据来说,ForEach 的键函数使用 item.id 即可。真实业务还应处理权限未开放、入口下线和按钮不可用状态,那些状态需要独立的播报文案,而不能只用灰色表达。

断点明确写出来,别赌默认行为

华为官方 GridRow/GridCol 文档将它们作为响应式栅格容器与子项,提供 columns、span、gutter 等布局参数。一个容易漏掉的版本细节是默认 columns 行为在新旧 API 版本中存在差异;如果演示依赖默认值,团队升级 SDK 后,实际每行能放几张卡片可能发生变化。因此本例直接指定断点对应列数:xs 和 sm 用四列,md 和 lg 用八列;每张卡片在所有断点都占四列。窄窗口只能放一张,宽窗口并排两张。这个表达方式比到处写 if (isFolded) 更关注应用可用空间。

@Entry
@Component
struct Index {
  @State activeId: string = 'A-02';
  private cards: ActionCard[] = SAMPLE_CARDS;

  build() {
    Column({ space: 12 }) {
      Text('大字卡片适配').fontSize(24).fontWeight(FontWeight.Bold)
      GridRow({
        columns: { xs: 4, sm: 4, md: 8, lg: 8 },
        gutter: { x: 12, y: 12 }
      }) {
        ForEach(this.cards, (item: ActionCard) => {
          GridCol({ span: { xs: 4, sm: 4, md: 4, lg: 4 } }) {
            Column({ space: 8 }) {
              Text(`${item.id}  ${item.title}`).fontSize(18)
              Button(item.action)
                .height(56)
                .accessibilityText(`${item.title},${item.action}`)
                .accessibilityDescription(item.hint)
                .onClick(() => { this.activeId = item.id; })
            }
            .padding(12)
            .width('100%')
            .border({ width: this.activeId === item.id ? 2 : 1,
                      color: this.activeId === item.id ? '#267BEE' : '#DDDDDD' })
          }
        }, (item: ActionCard) => item.id)
      }
    }
    .padding(16)
    .width('100%')
  }
}

GridRow 的列数指栅格单位数量,不是物理屏幕能同时显示几个卡片;每张卡片占四格,最终列数要用容器总格数除以 span 才能理解。gutter 则为相邻卡片留出间距,不能用一个很大的空白文本元素强行撑开布局。选择态通过 activeId 保存为业务 ID,而不是“左上角那张卡片”。这样从 760vp 缩回 360vp,即使元素换了位置,被选中仍然是 A-02。

还有一个经常被误解的点:配置了 accessibilityText 并不意味着无障碍工作结束。该属性能为控件提供朗读文本,accessibilityDescription 解释进一步的作用;而视觉层的 Text、按钮、对比度、焦点顺序、最小触控目标,仍需在目标设备逐项检查。在本例里,四个按钮可见文字都叫“查看”,却分别设置“文档管理,查看”“图片处理,查看”等上下文标签。不要在标签里堆叠“按钮”“双击即可操作”等词,朗读服务本身可能会补充控件角色。

大字场景不能只盯着截图

设计稿中的系统字体 1.6 倍只是回归测试的输入场景,不是上述代码调用了一个未经核实的系统设置接口。实际验收应在系统设置中调整字体规模,再观察组件是否按平台行为重新布局。固定高度和过小的容器容易剪掉第二行文字;本文仅给交互按钮设一个清晰的示意高度,卡片 Column 本身不设死高度,让标题能够扩展。若项目要支持极大字体、复杂本地化字符串或屏幕缩放,还需要增加滚动容器并结合真实宽度、字高检查碰撞。

为了避免“我看着像两列”变成唯一证据,增加一段不依赖系统功能的静态预期函数。它按照我们配置的四格/八格断点推算列数,并检查四张卡片的 title、action、hint 是否为空。这个函数只是测试用的契约校验,不属于 ArkUI 或系统无障碍 API,更不能替代真实聚焦和朗读顺序的体验测试。

// 本段是项目自定义的预期检查函数,不是系统无障碍测试接口。
function expectedColumns(widthVp: number): number {
  return widthVp < 600 ? 1 : 2; // 本例 GridRow 四列/八列配置
}
function auditLabels(cards: ActionCard[]): number {
  return cards.filter((c: ActionCard) =>
    c.title.trim().length > 0 &&
    c.action.trim().length > 0 &&
    c.hint.trim().length > 0
  ).length;
}
// 模拟输入:360vp -> 1 列;760vp -> 2 列;4 张卡片 -> 标签 4/4

由此可得到可人工复核的构造预期:360vp → 1 COL,760vp → 2 COL,可读标签填充情况为 LABEL 4/4;交互中点击 A-02 后切换窗口,仍应显示 A-02 选中。这些指标不包含渲染耗时、卡顿次数或读屏通过率,因为没有真实测量就不能编出数据。开发示意图中的底部 HiLog 文本同样是与 Demo 对应的构造日志,不代表已经成功编译,更不代表对系统读屏服务做了自动化断言。

如果要验证,建议按这条路径走

先固定数据排序和选中项,在常规字体、360vp 窄容器检查能否完整显示四张卡片,按钮触达是否明确。然后换成 760vp 预期的两列结构,确认 A-02 不会因为布局位置改变而失去选中态。第三次把系统字体改大到演示中的 1.6 倍,重点看标题换行、卡片高度、按钮是否被遮挡。第四次开启屏幕朗读,从 A-01 依次移动到 A-04,记录实际读到的标签、动作、描述,以及聚焦跳转顺序。只有这一步真正执行,才能对“朗读校验通过”下结论。

再加入一些恶意输入:删除 A-03 的 hint,静态 auditLabels 应从 4 降到 3;把两条 ID 都改成 A-02,开发阶段就应增加唯一性校验;把“图片处理”换成很长的多语言文字,看是否触发截断;旋转设备或把窗口拖到断点附近,确认重排不会使按钮瞬间失去响应。这些情况比单看一张展开态图片更接近实际线上反馈,同时能分清数据错误、布局错误与系统无障碍行为差异。

对于朗读质量,自动脚本最多能证明字段存在,不能证明语速、焦点可达性和朗读顺序符合真实用户的理解。真机验证还需要对比不同系统设置、设备形态、SDK 版本,并检查读屏器对可见 Text 与控件标签组合的处理方式。把全部检查概括为一个“无障碍通过率”反而会掩盖关键问题。建议在测试单中保留“视觉布局”“点击目标”“朗读内容”“焦点移动”“大字体截断”五个独立结论。

边界与结论

本例真正可迁移的东西是三项:栅格断点显式配置、业务选择态基于稳定 ID、可见动作与无障碍语义同时存在。它没有实现系统字体倍率读取,没有接入专有折叠状态 API,也没有宣称 360vp 或 760vp 能代表全部折叠设备。对特殊铰链姿态、悬停窗口、超大字号和不同语言,团队仍须补足设备矩阵。把入口语义在数据层写清楚,通常比不断给页面添加条件分支更容易维护。

官方参考与核对日期(2026-10-20): 华为 GridRow/GridCol 响应式布局指南(更新日期 2026-09-08)https://developer.huawei.com/consumer/en/doc/harmonyos-guides/arkts-layout-development-grid-layout;屏幕朗读适配指南 https://developer.huawei.com/consumer/en/doc/harmonyos-guides-V14/screen-reading-adapt-guide-V14。

Logo

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

更多推荐