手把手教你搞定 HarmonyOS 无障碍属性开发,让应用更具包容性
在移动应用开发中,无障碍功能常常被忽视,但它却是连接不同用户群体的重要桥梁。想象一下,视障用户通过屏幕朗读功能使用我们的应用时,能否清晰理解界面元素?今天咱们就来聊聊 HarmonyOS 中那些让应用更具包容性的无障碍属性,从基础设置到高级技巧,带大家一步步打造友好的无障碍交互体验。
一、无障碍分组:让组件成为一个整体
有时候我们需要让一组组件在无障碍服务中被当作一个整体处理,比如一个卡片内的多个元素,这时候就需要用到accessibilityGroup属性。
@Entry
@Component
struct AccessibilityGroupExample {
build() {
Column {
Image($r("app.media.icon"))
.width(80)
.height(80)
Text('天气卡片')
.fontSize(18)
.fontWeight(FontWeight.Bold)
Text('25℃ | 多云')
.fontSize(16)
}
.width(200)
.height(150)
.padding(15)
.backgroundColor(Color.White)
.borderRadius(10)
.shadow({ radius: 5, color: '#33000000' })
.accessibilityGroup(true) // 启用无障碍分组
.accessibilityText('天气信息卡片') // 设置分组的无障碍文本
}
}
重点总结:
- 分组逻辑:
accessibilityGroup(true)会将组件及其子组件视为一个整体,无障碍服务不再关注子组件内容。默认不启用分组,子组件会被单独识别。 - 文本拼接规则:如果分组组件没有无障碍文本且不含文本属性,会自动拼接子组件的通用文本(深度优先)。比如上面的天气卡片,若不设置
accessibilityText,会拼接 "天气卡片" 和 "25℃ | 多云" 作为朗读内容。 - API 版本注意:从 API version 10 开始支持,API version 14 后新增
accessibilityOptions参数,可设置accessibilityPreferred: true来优先拼接子组件的无障碍文本。
二、无障碍文本:给组件加上 "语音标签"
对于没有文本属性的组件,比如图片、图标,我们需要通过accessibilityText告诉屏幕朗读该播报什么内容。
@Entry
@Component
struct AccessibilityTextExample {
@State isPlaying: boolean = false;
build() {
Button(isPlaying ? '暂停播放' : '开始播放')
.width(120)
.height(48)
.backgroundColor(isPlaying ? Color.Gray : Color.Blue)
.borderRadius(24)
.accessibilityLevel('yes') // 确保组件可被无障碍服务识别
.accessibilityText(isPlaying ? '暂停音频播放按钮' : '开始音频播放按钮') // 动态更新无障碍文本
.onClick(() => {
this.isPlaying = !this.isPlaying;
})
}
}
重点总结:
- 核心作用:当组件没有文本属性时,
accessibilityText是屏幕朗读的关键信息源。如果组件同时有文本和无障碍文本,优先播报无障碍文本。 - 动态更新:如示例所示,无障碍文本可以根据组件状态动态变化,确保朗读内容始终与界面状态一致。
- 资源引用:从 API version 12 开始支持
accessibilityText(Resource),可以引用字符串资源文件,方便多语言适配。
三、无障碍说明:补充组件的详细解释
有时候光有文本还不够,比如一个危险操作按钮,需要额外说明后果,这时候accessibilityDescription就派上用场了。
@Entry
@Component
struct AccessibilityDescriptionExample {
build() {
Button('删除账户')
.width(160)
.height(50)
.backgroundColor(Color.Red)
.fontColor(Color.White)
.borderRadius(25)
.accessibilityLevel('yes')
.accessibilityText('删除账户按钮')
.accessibilityDescription('点击后将永久删除您的账户及所有数据,此操作不可恢复') // 详细说明操作后果
}
}
重点总结:
- 信息层级:当组件同时有文本、无障碍文本和说明时,播报顺序是:文本 → 无障碍文本 → 说明。示例中会先播报 "删除账户按钮",再播报说明内容。
- 场景应用:主要用于解释操作的潜在后果,尤其是那些无法从组件外观直接判断的风险操作。
- 资源引用支持:同
accessibilityText,从 API version 12 开始支持引用资源文件。
四、无障碍重要性:控制组件的可识别性
有些组件不需要被无障碍服务识别,比如装饰性元素,这时候可以用accessibilityLevel来控制。
@Entry
@Component
struct AccessibilityLevelExample {
build() {
Column {
// 重要按钮,必须可识别
Button('提交订单')
.accessibilityLevel('yes')
.accessibilityText('提交订单按钮,确认购买')
// 装饰性图标,无需识别
Image($r("app.media.decorative_icon"))
.width(30)
.height(30)
.accessibilityLevel('no') // 设置为不可识别
// 分组内的子组件,随分组状态变化
Text('订单编号:123456')
.accessibilityLevel('auto') // 由系统自动判断
}
.width('100%')
.padding(20)
}
}
重点总结:
- 取值说明:
yes:必须被识别no:不可识别no-hide-descendants:当前组件及所有子组件都不可识别auto:由系统根据分组等条件判断(默认值)
- 继承规则:如果父组件设置为
no-hide-descendants,子组件无论怎么设置都会被屏蔽。 - 分组影响:当父组件启用无障碍分组(
accessibilityGroup(true)),子组件的accessibilityLevel设置会被忽略。
五、无障碍虚拟节点:为自绘制组件提供信息
对于自定义绘制的组件(如 Canvas),无障碍服务无法自动获取内容,这时候需要用accessibilityVirtualNode手动提供节点信息。
@Entry
@Component
struct AccessibilityVirtualNodeExample {
@Builder VirtualContent() {
Column {
Text('图表标题:月度销售数据')
.fontSize(16)
.fontWeight(FontWeight.Bold)
Text('横轴:月份,纵轴:销售额(万元)')
.fontSize(14)
.fontColor(Color.Gray)
}
.width(200)
.padding(10)
}
build() {
Canvas()
.width('100%')
.height(200)
.onDraw((context) => {
// 绘制图表的逻辑
context.fillColor('#F0F0F0');
context.fillRect(0, 0, this.width, this.height);
// ... 省略具体绘制代码
})
.accessibilityLevel('yes')
.accessibilityVirtualNode(this.VirtualContent) // 提供虚拟节点信息
}
}
重点总结:
- 适用场景:自绘制组件(如 Canvas、自定义图形)必须通过
accessibilityVirtualNode提供可朗读内容,否则无障碍服务无法获取信息。 - 节点规则:虚拟节点中的组件仅用于无障碍信息获取,不会实际显示在界面上,但需要保证结构清晰。
- 数据同步:如果自绘制内容动态变化,虚拟节点的内容也需要相应更新,确保朗读信息准确。
六、选中状态管理:支持单选和多选场景
在列表、设置页等场景中,需要明确组件的选中状态,HarmonyOS 提供了accessibilityChecked(多选)和accessibilitySelected(单选)两个属性。
@Entry
@Component
struct SelectionStateExample {
@State selectedItems: Set<number> = new Set([1, 3]); // 多选状态
@State currentTab: number = 0; // 单选状态
build() {
Column {
// 多选框组
Text('喜欢的水果:')
.fontSize(16)
.marginBottom(10)
Row {
Checkbox('苹果')
.isChecked(this.selectedItems.has(1))
.onChange((isChecked) => {
if (isChecked) this.selectedItems.add(1);
else this.selectedItems.delete(1);
})
.accessibilityLevel('yes')
.accessibilityChecked(this.selectedItems.has(1)) // 同步多选状态
Checkbox('香蕉')
.isChecked(this.selectedItems.has(2))
.onChange((isChecked) => {
if (isChecked) this.selectedItems.add(2);
else this.selectedItems.delete(2);
})
.accessibilityLevel('yes')
.accessibilityChecked(this.selectedItems.has(2))
}
.width('100%')
.marginBottom(30)
// 单选标签页
Row {
Text('标签页1')
.fontSize(16)
.padding(10)
.backgroundColor(this.currentTab === 0 ? Color.Blue : Color.Gray)
.fontColor(Color.White)
.accessibilityLevel('yes')
.accessibilitySelected(this.currentTab === 0) // 同步单选状态
.onClick(() => this.currentTab = 0)
Text('标签页2')
.fontSize(16)
.padding(10)
.backgroundColor(this.currentTab === 1 ? Color.Blue : Color.Gray)
.fontColor(Color.White)
.accessibilityLevel('yes')
.accessibilitySelected(this.currentTab === 1)
.onClick(() => this.currentTab = 1)
}
.width('100%')
}
.width('100%')
.padding(20)
}
}
重点总结:
- 模式区别:
accessibilityChecked:用于多选场景,可同时选中多个组件accessibilitySelected:用于单选场景,同一组内只能有一个选中
- 状态同步:必须与组件的实际选中状态保持一致,否则会导致无障碍服务识别错误。
- 冲突处理:不能同时使用这两个属性,会造成状态冲突。如果需要切换模式,必须先将另一个属性设为
undefined。
七、组件角色定义:自定义屏幕朗读方式
有时候系统默认的朗读方式不符合需求,比如一个自定义的滑块组件,我们可以用accessibilityRole指定其角色类型。
@Entry
@Component
struct AccessibilityRoleExample {
@State volume: number = 50;
@Builder VolumeSlider() {
Row {
Text('音量调节')
.fontSize(14)
Text(`${this.volume}%`)
.fontSize(14)
.marginStart(10)
}
.width('100%')
}
build() {
Column {
// 自定义滑块组件,指定为滑块角色
Stack {
Rectangle()
.width('90%')
.height(8)
.backgroundColor(Color.Gray)
.cornerRadius(4)
Rectangle()
.width(`${this.volume * 0.9}%`)
.height(8)
.backgroundColor(Color.Blue)
.cornerRadius(4)
Circle()
.width(20)
.height(20)
.backgroundColor(Color.White)
.border({ width: 2, color: Color.Blue })
.marginStart(`${this.volume * 0.9 - 10}%`)
.shadow({ radius: 5, color: '#33000000' })
}
.width('100%')
.height(40)
.accessibilityLevel('yes')
.accessibilityRole(AccessibilityRoleType.SLIDER) // 指定为滑块角色
.accessibilityText(`音量滑块,当前${this.volume}%`)
.onClick(() => {
// 模拟滑块交互
if (this.volume < 100) this.volume += 10;
else this.volume = 0;
})
// 自定义按钮,指定为开关角色
Toggle({ type: ToggleType.Switch, isOn: this.volume > 0 })
.onChange((isOn) => {
this.volume = isOn ? 50 : 0;
})
.accessibilityLevel('yes')
.accessibilityRole(AccessibilityRoleType.SWITCH) // 指定为开关角色
.accessibilityText(isOn ? '音量已开启' : '音量已关闭')
}
.width('100%')
.padding(20)
}
}
重点总结:
- 角色类型:
AccessibilityRoleType枚举提供了 124 种角色类型,包括按钮、滑块、开关、图表等,覆盖大部分常用组件。 - 朗读优化:指定角色后,屏幕朗读会使用该角色的标准朗读方式,比如滑块会播报 "音量滑块,当前 50%,左右滑动调整"。
- 自定义角色:如果枚举中没有合适的类型,可以使用
ROLE_NONE,但不建议,尽量选择最接近的标准角色。
八、焦点控制:管理无障碍走焦顺序
在复杂界面中,合理的焦点顺序能让视障用户更顺畅地导航,accessibilityNextFocusId和accessibilityDefaultFocus可以帮助我们精确控制焦点。
@Entry
@Component
struct FocusControlExample {
build() {
Column {
Text('用户名:')
.fontSize(16)
.marginBottom(5)
TextInput({ placeholder: '请输入用户名' })
.id('username')
.width('80%')
.accessibilityLevel('yes')
.accessibilityNextFocusId('password') // 下一个焦点是密码输入框
Text('密码:')
.fontSize(16)
.margin({ top: 20, bottom: 5 })
TextInput({ placeholder: '请输入密码' })
.id('password')
.width('80%')
.accessibilityLevel('yes')
.accessibilityNextFocusId('remember') // 下一个焦点是记住密码
Row {
Checkbox('记住密码')
.id('remember')
.accessibilityLevel('yes')
.accessibilityNextFocusId('login') // 下一个焦点是登录按钮
Button('登录')
.id('login')
.width(100)
.accessibilityLevel('yes')
.accessibilityDefaultFocus(true) // 设置为默认首焦点
}
.width('100%')
.marginTop(20)
}
.width('100%')
.padding(20)
}
}
重点总结:
- 默认首焦点:
accessibilityDefaultFocus(true)设置组件为页面加载时的默认焦点,通常用于第一个可操作元素。 - 走焦顺序:
accessibilityNextFocusId('组件id')指定按下焦点键后的下一个焦点组件,id 必须唯一且存在。 - 跨进程场景:对于嵌入式组件(如
EmbeddedComponent),可能需要配合accessibilityUseSamePage解决跳焦问题,避免焦点在进程间丢失。
九、滚动控制与焦点样式:细节优化提升体验
最后,还有一些细节属性可以进一步优化无障碍体验,比如滚动触发和焦点框样式。
@Entry
@Component
struct ScrollAndFocusExample {
@State listData: string[] = Array(20).fill('').map((_, i) => `列表项 ${i + 1}`);
build() {
List {
ForEach(this.listData, (item, index) => {
ListItem {
Text(item)
.fontSize(16)
.padding(15)
.accessibilityLevel('yes')
}
.accessibilityScrollTriggerable(true) // 支持滚动触发
})
}
.width('100%')
.height(300)
.accessibilityScrollTriggerable(false) // 整个列表不自动滚动,由子项控制
// 焦点框绘制在顶层,避免被遮挡
Text('重要按钮')
.width(120)
.height(48)
.backgroundColor(Color.Blue)
.fontColor(Color.White)
.borderRadius(24)
.accessibilityLevel('yes')
.accessibilityFocusDrawLevel(FocusDrawLevel.TOP) // 焦点框绘制在顶层
}
}
重点总结:
- 滚动触发:
accessibilityScrollTriggerable(true)表示当焦点切换到容器但当前页面无可聚焦组件时,自动滚动到下一个可聚焦组件。建议在 List、Grid 等滚动组件上设置。 - 焦点框层级:
accessibilityFocusDrawLevel(FocusDrawLevel.TOP)将焦点绿框绘制在 Z 序顶层,避免被父组件或兄弟组件遮挡,提升可见性。 - 跨进程场景:
accessibilityUseSamePage用于解决嵌入式组件的跳焦问题,根据场景选择SEMI_SILENT或FULL_SILENT模式忽略部分 page 事件。
十、总结:构建包容性应用的核心要点
通过上面的讲解,相信大家对 HarmonyOS 的无障碍属性有了全面的认识。咱们再梳理一下开发时的核心要点:
- 基础三要素:
accessibilityText(必填文本)、accessibilityDescription(补充说明)、accessibilityLevel(可识别性)是最基础的无障碍属性,任何组件都应优先配置。 - 分组与聚焦:合理使用
accessibilityGroup整合相关组件,通过accessibilityNextFocusId和accessibilityDefaultFocus优化焦点导航路径。 - 状态与角色:
accessibilityChecked/Selected同步选中状态,accessibilityRole指定组件类型,让屏幕朗读更准确。 - 自绘与滚动:自绘制组件必须通过
accessibilityVirtualNode提供信息,滚动容器合理设置accessibilityScrollTriggerable。 - 细节优化:
accessibilityFocusDrawLevel确保焦点框可见,accessibilityUseSamePage解决跨进程跳焦问题。
无障碍开发不是额外的负担,而是让应用惠及更多用户的重要途径。从现在开始,在项目中逐步加入这些属性,不仅能提升应用的包容性,还能满足更多场景的开发需求。记得根据 API 版本适配不同功能,参考官方文档确认参数支持情况,让我们一起打造更友好的 HarmonyOS 应用体验!
更多推荐

所有评论(0)