《无障碍读屏》一、无障碍属性使用指南
HarmonyOS Next ArkTS 无障碍属性完全指南:从零到精通打造读屏友好应用
摘要:本文系统讲解 HarmonyOS (API 23+)中 ArkUI 无障碍属性的核心概念、使用方法和最佳实践。通过丰富的代码示例和对比分析,帮助开发者掌握
accessibilityText、accessibilityDescription、accessibilityGroup、accessibilityRole、accessibilityState等关键属性的实战技巧,构建对视障用户真正友好的应用。
效果
一、为什么需要无障碍开发?
根据世界卫生组织数据,全球约有 2.85 亿 视障人群。在 HarmonyOS 生态中,屏幕朗读(TalkBack) 是视障用户使用手机的核心辅助工具。
然而,很多开发者对无障碍的认知停留在"加个 accessibilityText"的阶段。实际上,HarmonyOS 提供了一套完整的无障碍属性体系,正确使用可以大幅提升应用的可用性。
无障碍开发的核心目标:
- 让屏幕朗读器能准确理解每个 UI 元素的语义
- 让用户能高效导航整个应用界面
- 让交互操作有清晰的反馈机制
二、无障碍属性全景图
在 ArkTS 中,无障碍属性分为以下几大类:
| 属性分类 | 核心属性 | 作用 |
|---|---|---|
| 内容描述 | accessibilityText |
读屏器朗读的核心文本 |
| 操作提示 | accessibilityDescription |
告知用户如何操作 |
| 角色类型 | accessibilityRole |
告诉读屏器这是什么类型的控件 |
| 状态信息 | accessibilityState |
描述控件的当前状态 |
| 节点分组 | accessibilityGroup |
将多个子节点合并为一个焦点单元 |
| 层级控制 | accessibilityLevel |
控制节点是否参与无障碍遍历 |
三、accessibilityText:读屏的第一道声音
3.1 基本用法
accessibilityText 是最基础也是最常用的无障碍属性,它为 UI 元素提供文本描述,屏幕朗读器会朗读这段文本。
// 基础文本描述
Image($r('app.media.avatar'))
.accessibilityText('用户头像')
Button('提交订单')
.accessibilityText('提交订单')
3.2 核心原则:不重复已有信息
最常见的错误是在 accessibilityText 中重复系统已知的信息(如角色、状态):
// ❌ 错误示范:重复了 Button 的角色信息
Button('提交')
.accessibilityText('按钮 提交表单')
// 读屏器实际朗读:"按钮 按钮 提交表单 按钮"
// ✅ 正确写法:只提供语义内容
Button('提交')
.accessibilityText('提交表单')
// 读屏器朗读:"提交表单 按钮"
3.3 图片的无障碍描述
图片是最容易遗漏无障碍描述的元素:
// 装饰性图片 - 不需要读屏
Image($r('app.media.divider_line'))
.accessibilityLevel('no-hide-descendants')
// 信息性图片 - 需要详细描述
Image($r('app.media.qr_code'))
.accessibilityText('商家收款二维码,可双击放大查看')
.accessibilityRole(AccessibilityRoleType.IMAGE)
// 图标按钮 - 描述功能而非外观
Image($r('app.media.icon_search'))
.accessibilityText('搜索') // ✅ 描述功能
// .accessibilityText('放大镜图标') // ❌ 描述外观无意义
3.4 敏感信息的无障碍处理
对于手机号、银行卡号等敏感信息,应使用中文数字替代阿拉伯数字:
// 展示文本:13912345678
// 无障碍文本:一三九 一二三四 五六七八
Text('13912345678')
.accessibilityText('一三九 一二三四 五六七八')
// 银行卡号后四位
Text('**** **** **** 6789')
.accessibilityText('卡号后四位 六七八九')
四、accessibilityDescription:操作提示的艺术
accessibilityDescription 为可交互元素提供操作提示。当设置此属性时,它会替换系统默认的"单指双击即可执行"提示。
4.1 基本用法
Button('删除')
.accessibilityText('删除笔记')
.accessibilityDescription('双击删除当前笔记,此操作不可撤销')
.accessibilityRole(AccessibilityRoleType.BUTTON)
4.2 复杂交互的提示
// 滑动操作
Row() {
Text('消息列表项')
}
.accessibilityText('来自张三的消息')
.accessibilityDescription('双击打开消息,左滑删除,右滑标记已读')
// 长按操作
Column() {
Text('图片')
}
.accessibilityText('拍摄于2025年12月的风景照片')
.accessibilityDescription('双击查看大图,长按可分享或保存')
最佳实践:操作提示应该简洁、准确,告知用户核心交互方式即可,避免冗长的说明。
五、accessibilityRole:让读屏器理解控件类型
5.1 角色类型概览
AccessibilityRoleType 枚举提供了丰富的控件类型:
// 交互控件
AccessibilityRoleType.BUTTON // 按钮
AccessibilityRoleType.SWITCH // 开关
AccessibilityRoleType.CHECK_BOX // 复选框
AccessibilityRoleType.RADIO_BUTTON // 单选按钮
AccessibilityRoleType.SLIDER // 滑块
AccessibilityRoleType.TEXT_FIELD // 文本输入框
// 信息展示
AccessibilityRoleType.IMAGE // 图片
AccessibilityRoleType.TEXT // 文本
AccessibilityRoleType.HEADING // 标题(导航地标)
AccessibilityRoleType.LIST // 列表容器
AccessibilityRoleType.LIST_ITEM // 列表项
5.2 正确使用角色
// 标题元素 - 读屏器支持快速跳转到标题
Text('个人信息')
.fontSize(24)
.fontWeight(FontWeight.Bold)
.accessibilityRole(AccessibilityRoleType.HEADING)
// 开关控件
Toggle({ type: ToggleType.Switch, isOn: this.isDarkMode })
.accessibilityText('深色模式')
.accessibilityRole(AccessibilityRoleType.SWITCH)
.accessibilityState({ isOn: this.isDarkMode })
// 自定义按钮
Column() {
Image($r('app.media.icon'))
Text('拍照')
}
.accessibilityRole(AccessibilityRoleType.BUTTON)
.accessibilityText('拍照')
5.3 为什么角色很重要?
角色类型不仅影响朗读内容,还影响读屏器的交互模式:
- 设为
BUTTON:读屏器会提示"双击执行" - 设为
SWITCH:读屏器会提示"双击切换" - 设为
HEADING:用户可以通过"标题导航"快速跳转
六、accessibilityState:状态信息的精确传达
6.1 checked vs selected 的区别
这是最容易混淆的两个状态属性:
| 属性 | 适用场景 | 状态切换朗读 |
|---|---|---|
checked |
复选框(多选) | 每次切换都朗读 |
selected |
单选按钮(单选) | 仅首次聚焦朗读 |
// 复选框 - 使用 checked
Checkbox()
.accessibilityText('记住密码')
.accessibilityRole(AccessibilityRoleType.CHECK_BOX)
.accessibilityState({
checked: this.rememberPassword,
checkedChange: (checked: boolean) => {
this.rememberPassword = checked
}
})
// 勾选时朗读:"已选中"
// 取消时朗读:"未选中"
// 单选按钮 - 使用 selected
Radio({ value: 'male', group: 'gender' })
.accessibilityText('男')
.accessibilityRole(AccessibilityRoleType.RADIO_BUTTON)
.accessibilityState({
selected: this.gender === 'male',
selectedChange: (selected: boolean) => {
if (selected) this.gender = 'male'
}
})
6.2 开关与滑块状态
// Switch 开关
Toggle({ type: ToggleType.Switch, isOn: this.wifiEnabled })
.accessibilityText('Wi-Fi')
.accessibilityRole(AccessibilityRoleType.SWITCH)
.accessibilityState({
isOn: this.wifiEnabled,
selectedChange: (selected: boolean) => {
this.wifiEnabled = selected
}
})
// Slider 滑块
Slider({ value: this.volume, min: 0, max: 100 })
.accessibilityText('媒体音量')
.accessibilityRole(AccessibilityRoleType.SLIDER)
.accessibilityState({
currentValue: this.volume,
minValue: 0,
maxValue: 100
})
七、accessibilityGroup:焦点合并的智慧
7.1 问题:碎片化焦点
没有分组的复合 UI 元素,每个子元素都会单独获得焦点,导致读屏体验碎片化:
// ❌ 没有分组:每个元素单独聚焦,读屏器朗读4次
Row() {
Image($r('app.media.avatar')) // 焦点1:朗读"头像"
Text('李华') // 焦点2:朗读"李华"
Text('产品经理') // 焦点3:朗读"产品经理"
Text('4.8分') // 焦点4:朗读"4.8分"
}
7.2 解决方案:使用 accessibilityGroup
// ✅ 使用分组:合并为一个焦点,读屏器朗读1次
Row() {
Image($r('app.media.avatar'))
Text('李华').fontSize(18).fontWeight(FontWeight.Bold)
Text('产品经理').fontColor('#666666')
Text('4.8 ★').fontColor('#FFD740')
}
.accessibilityGroup(true)
.accessibilityText('李华,产品经理,评分4.8星')
.accessibilityRole(AccessibilityRoleType.LIST_ITEM)
7.3 使用原则
| 场景 | 是否分组 | 说明 |
|---|---|---|
| 联系人卡片 | ✅ 是 | 多个信息构成一个整体 |
| 列表项 | ✅ 是 | 图标+标题+描述是一体的 |
| 表单按钮组 | ❌ 否 | 每个按钮需要独立操作 |
| 导航栏图标 | ❌ 否 | 每个图标是独立功能入口 |
八、accessibilityLevel:控制无障碍遍历层级
8.1 四种层级模式
// 'auto' - 系统自动决定(默认值)
Text('普通文本')
.accessibilityLevel('auto')
// 'yes' - 强制参与遍历
Text('重要公告')
.accessibilityLevel('yes')
// 'no' - 自身不参与,但子元素仍参与
Column() {
Text('装饰性标题框') // 不会被朗读
.accessibilityLevel('no')
Text('核心内容') // 仍然会被朗读
.accessibilityLevel('yes')
}
// 'no-hide-descendants' - 自身和所有子元素都不参与
Column() {
Image($r('app.media.decoration'))
Text('v2.0.1')
}
.accessibilityLevel('no-hide-descendants') // 整个区域对读屏器不可见
8.2 实际应用:装饰性元素
// 背景装饰图 - 不需要读屏
Image($r('app.media.bg_pattern'))
.accessibilityLevel('no-hide-descendants')
// 版本号 - 对普通用户有意义,对读屏用户干扰
Text('Version 3.2.1')
.accessibilityLevel('no')
// 分隔线 - 纯装饰
Divider()
.accessibilityLevel('no-hide-descendants')
九、主动播报:sendAccessibilityEvent
9.1 场景说明
有些信息变化不会触发焦点变化(如 Toast 提示、加载状态),此时需要主动播报让用户知晓:
import { accessibility } from '@kit.AccessibilityKit'
// 操作成功提示
submitOrder(): void {
// 提交订单逻辑...
accessibility.sendAccessibilityEvent(
this.getContext(),
'announceForAccessibility',
{ text: '订单提交成功,预计3个工作日内发货' }
)
}
// 加载进度通知
loadData(): void {
accessibility.sendAccessibilityEvent(
this.getContext(),
'announceForAccessibility',
{ text: '正在加载数据,请稍候' }
)
}
9.2 请求焦点转移
// 表单验证失败时,将焦点移到出错的输入框
validateForm(): void {
if (!this.isValidEmail) {
accessibility.sendAccessibilityEvent(
this.getContext(),
'announceForAccessibility',
{ text: '邮箱格式不正确,请重新输入' }
)
accessibility.sendAccessibilityEvent(
this.getContext(),
'requestFocusForAccessibility',
{ targetId: 'emailInput' }
)
}
}
十、完整示例:无障碍表单页面
下面是一个完整的无障碍友好型表单页面示例:
@ComponentV2
struct AccessibleFormPage {
@Local username: string = ''
@Local password: string = ''
@Local rememberMe: boolean = false
@Local showPassword: boolean = false
build() {
Column() {
// 标题区域
Text('用户登录')
.fontSize(28)
.fontWeight(FontWeight.Bold)
.accessibilityText('用户登录')
.accessibilityRole(AccessibilityRoleType.HEADING)
.margin({ top: 40, bottom: 30 })
// 用户名输入框
TextInput({ placeholder: '请输入用户名', text: this.username })
.id('usernameInput')
.accessibilityText('用户名输入框')
.accessibilityDescription('输入您的用户名用于登录')
.accessibilityRole(AccessibilityRoleType.TEXT_FIELD)
.onChange((value: string) => { this.username = value })
// 密码输入框
TextInput({
placeholder: '请输入密码',
text: this.password
})
.id('passwordInput')
.type(this.showPassword ? InputType.Normal : InputType.Password)
.accessibilityText('密码输入框')
.accessibilityDescription('输入您的登录密码')
.accessibilityRole(AccessibilityRoleType.TEXT_FIELD)
.onChange((value: string) => { this.password = value })
// 显示密码开关
Row() {
Toggle({ type: ToggleType.Switch, isOn: this.showPassword })
.accessibilityText('显示密码')
.accessibilityRole(AccessibilityRoleType.SWITCH)
.accessibilityState({
isOn: this.showPassword,
selectedChange: (selected: boolean) => {
this.showPassword = selected
}
})
Text('显示密码')
.margin({ left: 8 })
}
.accessibilityGroup(true)
// 记住密码复选框
Row() {
Checkbox()
.accessibilityText('记住密码')
.accessibilityRole(AccessibilityRoleType.CHECK_BOX)
.accessibilityState({
checked: this.rememberMe,
checkedChange: (checked: boolean) => {
this.rememberMe = checked
}
})
Text('记住密码')
.margin({ left: 8 })
}
.accessibilityGroup(true)
// 登录按钮
Button('登录')
.accessibilityText('登录')
.accessibilityDescription('双击提交登录信息')
.accessibilityRole(AccessibilityRoleType.BUTTON)
.onClick(() => {
// 登录逻辑
})
}
.padding(24)
.width('100%')
}
}
十一、无障碍测试清单
开发完成后,请按以下清单逐项检查:
基础检查
- 所有可交互元素都有
accessibilityText -
accessibilityText没有重复角色/状态信息 - 装饰性图片已设置
accessibilityLevel('no-hide-descendants') - 所有图片类型的功能按钮有功能描述
交互检查
- 所有按钮设置了正确的
accessibilityRole - 复选框使用
checked状态 - 单选按钮使用
selected状态 - 开关控件使用
isOn状态
结构检查
- 复合卡片使用了
accessibilityGroup(true) - 列表容器标记了
AccessibilityRoleType.LIST - 标题元素使用
accessibilityText提供清晰的区域描述
反馈检查
- 操作结果通过
sendAccessibilityEvent主动播报 - 表单验证错误信息能被读屏器获取
- 加载状态有语音提示
十二、实战踩坑总结
在实际项目开发中,以下问题经常遇到,请务必注意:
12.1 accessibilityState 的组件兼容性
重要提醒:accessibilityState 属性并非所有组件都支持!
| 组件类型 | 是否支持 accessibilityState |
说明 |
|---|---|---|
Checkbox / Toggle |
✅ 支持 | 内置状态管理 |
Radio |
✅ 支持 | 内置选中状态 |
Slider |
✅ 支持 | 内置范围状态 |
Text |
❌ 不支持 | 编译报错 |
Column / Row |
❌ 不支持 | 编译报错 |
Image |
❌ 不支持 | 编译报错 |
替代方案:对于不支持 accessibilityState 的组件,将状态信息合并到 accessibilityText 中:
// ❌ Text 不支持 accessibilityState
Text(this.isFavorite ? '★' : '☆')
.accessibilityState({ checked: this.isFavorite }) // 编译报错!
// ✅ 将状态信息融入 accessibilityText
Text(this.isFavorite ? '★' : '☆')
.accessibilityText(`收藏按钮,${this.isFavorite ? '已收藏' : '未收藏'},双击切换`)
.accessibilityRole(AccessibilityRoleType.BUTTON)
12.2 AccessibilityRoleType 枚举值的实际可用性
官方文档列出了大量枚举值,但并非所有值在当前 API 版本中都可用:
| 枚举值 | 实际可用性 | 替代方案 |
|---|---|---|
BUTTON |
✅ 可用 | — |
TEXT |
✅ 可用 | — |
TEXT_FIELD |
✅ 可用 | — |
LIST / LIST_ITEM |
✅ 可用 | — |
IMAGE |
✅ 可用 | — |
CHECK_BOX |
✅ 可用 | — |
SWITCH |
✅ 可用 | — |
HEADING |
❌ 不存在 | 使用 TEXT + 字体样式区分 |
SECTION |
❌ 不存在 | 使用 TEXT + accessibilityGroup |
SUMMARY |
❌ 不存在 | 使用 TEXT + accessibilityGroup |
建议:只使用确认存在的枚举值,用 accessibilityText 补充语义信息。
12.3 @ComponentV2 下 getContext() 不可用
@ComponentV2 组件没有 getContext() 方法,应使用 getUIContext():
// ❌ @ComponentV2 中不存在
@ComponentV2
struct MyPage {
build() {
Button('测试')
.onClick(() => {
let ctx = this.getContext() // 编译报错!
})
}
}
// ✅ 使用 getUIContext()
@ComponentV2
struct MyPage {
build() {
Button('测试')
.onClick(() => {
let ctx = this.getUIContext() // 正确
})
}
}
十三、总结
无障碍开发不是"锦上添花",而是每个开发者的责任。HarmonyOS ArkTS 提供的无障碍属性体系功能完善、使用简便:
accessibilityText—— 提供核心语义,不重复系统已知信息accessibilityDescription—— 补充操作提示,引导用户交互accessibilityRole—— 标识控件类型,影响朗读和交互模式accessibilityState—— 精确传达状态,区分 checked/selected/isOnaccessibilityGroup—— 合并焦点单元,提升导航效率accessibilityLevel—— 控制遍历层级,排除装饰性干扰sendAccessibilityEvent—— 主动播报动态变化
掌握这些属性并养成使用习惯,同时记住实战中的注意事项,你的应用将惠及更多用户。
参考文档:
更多推荐




所有评论(0)