鸿蒙无障碍开发完整指南【2】
2025-06-28 21:27:42
107次阅读
0个评论
第二篇:无障碍开发进阶与最佳实践
2.1 复杂组件的无障碍设计
在实际应用开发中,我们经常需要处理复杂的组件结构。合理的无障碍设计能够让这些复杂组件变得易于理解和操作。
2.1.1 列表组件的无障碍处理
列表是应用中最常见的组件之一,正确的无障碍设计能够让用户快速理解列表结构和内容。
interface NewsItem {
id: string
title: string
summary: string
author: string
publishTime: string
readCount: number
isTop: boolean
}
@Component
struct AccessibleNewsList {
@State newsList: NewsItem[] = [
{
id: '1',
title: '鸿蒙系统发布重大更新',
summary: '新版本带来了更多无障碍功能改进...',
author: '科技日报',
publishTime: '2024-01-15',
readCount: 1520,
isTop: true
},
{
id: '2',
title: '无障碍技术发展趋势',
summary: '探讨未来无障碍技术的发展方向...',
author: '技术周刊',
publishTime: '2024-01-14',
readCount: 890,
isTop: false
}
]
build() {
Column() {
// 列表标题
Text('最新资讯')
.fontSize(20)
.fontWeight(FontWeight.Bold)
.margin({ bottom: 16 })
.accessibilityLevel('yes')
.accessibilityText(`资讯列表标题:最新资讯,共${this.newsList.length}条`)
// 新闻列表
List() {
ForEach(this.newsList, (item: NewsItem, index: number) => {
ListItem() {
this.NewsItemComponent({
item: item,
index: index,
total: this.newsList.length
})
}
.margin({ bottom: 12 })
})
}
.width('100%')
.layoutWeight(1)
// 为整个列表提供无障碍说明
.accessibilityText('新闻列表')
.accessibilityDescription(`包含${this.newsList.length}条新闻,可上下滑动浏览`)
}
.padding(16)
.width('100%')
.height('100%')
}
@Builder
NewsItemComponent(params: { item: NewsItem, index: number, total: number }) {
Row() {
Column() {
// 置顶标识
if (params.item.isTop) {
Text('置顶')
.fontSize(10)
.fontColor('#FF4444')
.backgroundColor('#FFE5E5')
.padding({ horizontal: 6, vertical: 2 })
.borderRadius(4)
.margin({ bottom: 8 })
.accessibilityText('置顶标识')
}
// 新闻标题
Text(params.item.title)
.fontSize(16)
.fontWeight(FontWeight.Bold)
.maxLines(2)
.textOverflow({ overflow: TextOverflow.Ellipsis })
.margin({ bottom: 8 })
// 新闻摘要
Text(params.item.summary)
.fontSize(14)
.fontColor('#666666')
.maxLines(2)
.textOverflow({ overflow: TextOverflow.Ellipsis })
.margin({ bottom: 12 })
// 新闻元信息
Row() {
Text(params.item.author)
.fontSize(12)
.fontColor('#999999')
Text('·')
.fontSize(12)
.fontColor('#999999')
.margin({ horizontal: 8 })
Text(params.item.publishTime)
.fontSize(12)
.fontColor('#999999')
Text('·')
.fontSize(12)
.fontColor('#999999')
.margin({ horizontal: 8 })
Text(`${params.item.readCount}次阅读`)
.fontSize(12)
.fontColor('#999999')
}
.width('100%')
}
.layoutWeight(1)
.alignItems(HorizontalAlign.Start)
// 操作按钮
Image($r('app.media.ic_arrow_right'))
.width(20)
.height(20)
.margin({ left: 12 })
.accessibilityText('查看详情')
}
.width('100%')
.padding(16)
.backgroundColor('#FFFFFF')
.borderRadius(12)
.shadow({ radius: 4, color: '#00000010' })
// 将整个新闻项作为一个无障碍组
.accessibilityGroup(true)
.accessibilityLevel('yes')
// 提供完整的新闻信息描述
.accessibilityText(
`第${params.index + 1}条新闻,共${params.total}条。` +
`${params.item.isTop ? '置顶新闻,' : ''}` +
`标题:${params.item.title}。` +
`摘要:${params.item.summary}。` +
`来源:${params.item.author},` +
`发布时间:${params.item.publishTime},` +
`阅读量:${params.item.readCount}次。` +
`点击查看详情。`
)
.onClick(() => {
console.log(`点击新闻:${params.item.title}`)
// 导航到详情页面
})
}
}
2.1.2 表单组件的无障碍设计
表单是用户输入数据的重要界面,良好的无障碍设计能够帮助用户准确理解和填写表单。
interface FormData {
username: string
email: string
password: string
confirmPassword: string
agreeTerms: boolean
}
@Component
struct AccessibleForm {
@State formData: FormData = {
username: '',
email: '',
password: '',
confirmPassword: '',
agreeTerms: false
}
@State errors: Record<string, string> = {}
@State isSubmitting: boolean = false
// 表单验证
private validateForm(): boolean {
this.errors = {}
if (!this.formData.username.trim()) {
this.errors['username'] = '用户名不能为空'
}
if (!this.formData.email.trim()) {
this.errors['email'] = '邮箱不能为空'
} else if (!/\S+@\S+\.\S+/.test(this.formData.email)) {
this.errors['email'] = '邮箱格式不正确'
}
if (!this.formData.password) {
this.errors['password'] = '密码不能为空'
} else if (this.formData.password.length < 6) {
this.errors['password'] = '密码长度至少6位'
}
if (this.formData.password !== this.formData.confirmPassword) {
this.errors['confirmPassword'] = '两次密码输入不一致'
}
if (!this.formData.agreeTerms) {
this.errors['agreeTerms'] = '请同意用户协议'
}
return Object.keys(this.errors).length === 0
}
build() {
Scroll() {
Column() {
// 表单标题
Text('用户注册')
.fontSize(24)
.fontWeight(FontWeight.Bold)
.margin({ bottom: 24 })
.accessibilityLevel('yes')
.accessibilityText('用户注册表单')
.accessibilityDescription('请填写以下信息完成注册')
// 用户名输入
this.FormFieldComponent({
label: '用户名',
value: this.formData.username,
placeholder: '请输入用户名',
isRequired: true,
errorMessage: this.errors['username'],
onChange: (value: string) => {
this.formData.username = value
if (this.errors['username']) {
delete this.errors['username']
}
}
})
// 邮箱输入
this.FormFieldComponent({
label: '邮箱',
value: this.formData.email,
placeholder: '请输入邮箱地址',
isRequired: true,
inputType: InputType.Email,
errorMessage: this.errors['email'],
onChange: (value: string) => {
this.formData.email = value
if (this.errors['email']) {
delete this.errors['email']
}
}
})
// 密码输入
this.FormFieldComponent({
label: '密码',
value: this.formData.password,
placeholder: '请输入密码',
isRequired: true,
inputType: InputType.Password,
errorMessage: this.errors['password'],
helpText: '密码长度至少6位',
onChange: (value: string) => {
this.formData.password = value
if (this.errors['password']) {
delete this.errors['password']
}
}
})
// 确认密码输入
this.FormFieldComponent({
label: '确认密码',
value: this.formData.confirmPassword,
placeholder: '请再次输入密码',
isRequired: true,
inputType: InputType.Password,
errorMessage: this.errors['confirmPassword'],
onChange: (value: string) => {
this.formData.confirmPassword = value
if (this.errors['confirmPassword']) {
delete this.errors['confirmPassword']
}
}
})
// 用户协议复选框
Row() {
Checkbox()
.select(this.formData.agreeTerms)
.onChange((isChecked: boolean) => {
this.formData.agreeTerms = isChecked
if (this.errors['agreeTerms']) {
delete this.errors['agreeTerms']
}
})
.accessibilityText('用户协议复选框')
.accessibilityDescription(this.formData.agreeTerms ? '已同意用户协议' : '未同意用户协议')
Row() {
Text('我已阅读并同意')
.fontSize(14)
.margin({ left: 8 })
Text('《用户协议》')
.fontSize(14)
.fontColor('#3B82F6')
.margin({ left: 4 })
.onClick(() => {
console.log('查看用户协议')
})
.accessibilityText('用户协议链接')
.accessibilityDescription('点击查看用户协议详情')
}
}
.alignItems(VerticalAlign.Center)
.margin({ top: 16, bottom: 8 })
.accessibilityGroup(true)
.accessibilityText(`用户协议确认,${this.formData.agreeTerms ? '已同意' : '未同意'}`)
// 错误提示
if (this.errors['agreeTerms']) {
Text(this.errors['agreeTerms'])
.fontSize(12)
.fontColor('#FF4444')
.margin({ bottom: 16 })
.accessibilityText(`错误提示:${this.errors['agreeTerms']}`)
}
// 提交按钮
Button(this.isSubmitting ? '注册中...' : '注册')
.width('100%')
.height(50)
.backgroundColor(this.isSubmitting ? '#CCCCCC' : '#3B82F6')
.fontSize(16)
.fontColor(Color.White)
.enabled(!this.isSubmitting)
.margin({ top: 24 })
.accessibilityText(this.isSubmitting ? '正在注册,请稍候' : '注册按钮')
.accessibilityDescription(this.isSubmitting ? '注册请求正在处理中' : '点击提交注册信息')
.onClick(() => {
if (this.validateForm()) {
this.isSubmitting = true
// 模拟提交过程
setTimeout(() => {
this.isSubmitting = false
console.log('注册成功')
}, 2000)
}
})
}
.padding(20)
.width('100%')
}
.width('100%')
.height('100%')
}
@Builder
FormFieldComponent(params: {
label: string,
value: string,
placeholder: string,
isRequired?: boolean,
inputType?: InputType,
errorMessage?: string,
helpText?: string,
onChange: (value: string) => void
}) {
Column() {
// 标签行
Row() {
Text(params.label)
.fontSize(16)
.fontWeight(FontWeight.Medium)
if (params.isRequired) {
Text('*')
.fontSize(16)
.fontColor('#FF4444')
.margin({ left: 4 })
.accessibilityText('必填项标识')
}
}
.alignItems(VerticalAlign.Center)
.margin({ bottom: 8 })
.width('100%')
// 输入框
TextInput({
placeholder: params.placeholder,
text: params.value
})
.width('100%')
.height(50)
.borderRadius(8)
.border({
width: 1,
color: params.errorMessage ? '#FF4444' : '#E5E5E5'
})
.backgroundColor(params.errorMessage ? '#FFF5F5' : '#FFFFFF')
.type(params.inputType || InputType.Normal)
.onChange(params.onChange)
.accessibilityText(`${params.label}输入框`)
.accessibilityDescription(
`${params.isRequired ? '必填项,' : ''}` +
`请输入${params.label}` +
`${params.helpText ? ',' + params.helpText : ''}` +
`${params.errorMessage ? ',当前有错误:' + params.errorMessage : ''}`
)
// 帮助文本
if (params.helpText && !params.errorMessage) {
Text(params.helpText)
.fontSize(12)
.fontColor('#666666')
.margin({ top: 4 })
.accessibilityText(`帮助信息:${params.helpText}`)
}
// 错误信息
if (params.errorMessage) {
Text(params.errorMessage)
.fontSize(12)
.fontColor('#FF4444')
.margin({ top: 4 })
.accessibilityText(`错误提示:${params.errorMessage}`)
}
}
.alignItems(HorizontalAlign.Start)
.margin({ bottom: 20 })
.width('100%')
}
}
2.2 无障碍测试与调试
开发完成后,我们需要对无障碍功能进行充分的测试,确保所有用户都能正常使用我们的应用。
2.2.1 无障碍测试工具
@Component
struct AccessibilityTestHelper {
@State testResults: string[] = []
@State isTestRunning: boolean = false
// 模拟无障碍测试
private runAccessibilityTest() {
this.isTestRunning = true
this.testResults = []
// 模拟测试过程
const tests = [
'检查所有图片是否有无障碍文本...',
'验证表单字段的标签关联...',
'测试键盘导航顺序...',
'检查颜色对比度...',
'验证屏幕阅读器兼容性...'
]
tests.forEach((test, index) => {
setTimeout(() => {
this.testResults.push(`✓ ${test} 通过`)
if (index === tests.length - 1) {
this.isTestRunning = false
this.testResults.push('🎉 所有无障碍测试通过!')
}
}, (index + 1) * 1000)
})
}
build() {
Column() {
Text('无障碍测试工具')
.fontSize(20)
.fontWeight(FontWeight.Bold)
.margin({ bottom: 20 })
.accessibilityLevel('yes')
Button(this.isTestRunning ? '测试中...' : '开始无障碍测试')
.width('100%')
.height(50)
.backgroundColor(this.isTestRunning ? '#CCCCCC' : '#3B82F6')
.enabled(!this.isTestRunning)
.margin({ bottom: 20 })
.accessibilityText(this.isTestRunning ? '测试正在进行中' : '开始无障碍测试按钮')
.onClick(() => {
this.runAccessibilityTest()
})
// 测试结果
if (this.testResults.length > 0) {
Column() {
Text('测试结果:')
.fontSize(16)
.fontWeight(FontWeight.Medium)
.margin({ bottom: 12 })
.accessibilityText('测试结果标题')
ForEach(this.testResults, (result: string, index: number) => {
Text(result)
.fontSize(14)
.margin({ bottom: 8 })
.accessibilityText(`测试结果第${index + 1}项:${result}`)
})
}
.alignItems(HorizontalAlign.Start)
.width('100%')
.padding(16)
.backgroundColor('#F8F9FA')
.borderRadius(8)
.accessibilityGroup(true)
.accessibilityText(`测试结果区域,共${this.testResults.length}项结果`)
}
}
.padding(20)
.width('100%')
.height('100%')
}
}
2.3 无障碍开发最佳实践
2.3.1 设计原则
-
可感知性(Perceivable)
- 为所有非文本内容提供替代文本
- 确保足够的颜色对比度
- 支持文本缩放和语音输出
-
可操作性(Operable)
- 支持键盘和语音操作
- 提供合理的操作时间
- 避免引起癫痫的闪烁内容
-
可理解性(Understandable)
- 使用清晰简洁的语言
- 保持界面行为的一致性
- 提供错误提示和帮助信息
-
健壮性(Robust)
- 兼容各种辅助技术
- 遵循无障碍标准和规范
- 支持不同的设备和平台
2.3.2 开发检查清单
// 无障碍开发检查清单组件
@Component
struct AccessibilityChecklist {
@State checkedItems: boolean[] = new Array(20).fill(false)
private checklistItems = [
{ category: '基础要求', items: [
'所有图片都设置了accessibilityText',
'按钮和链接有明确的无障碍描述',
'表单字段有对应的标签',
'错误信息能被屏幕阅读器识别'
]},
{ category: '导航体验', items: [
'键盘导航顺序合理',
'焦点指示器清晰可见',
'跳过链接功能可用',
'页面标题准确描述内容'
]},
{ category: '视觉设计', items: [
'颜色对比度符合WCAG标准',
'不仅依赖颜色传达信息',
'文字可以放大到200%',
'界面在高对比度模式下可用'
]},
{ category: '交互反馈', items: [
'操作结果有明确反馈',
'加载状态有语音提示',
'错误处理友好',
'帮助信息易于获取'
]},
{ category: '兼容性测试', items: [
'屏幕阅读器测试通过',
'语音控制功能正常',
'开关控制设备兼容',
'放大镜功能支持'
]}
]
build() {
Scroll() {
Column() {
Text('无障碍开发检查清单')
.fontSize(24)
.fontWeight(FontWeight.Bold)
.margin({ bottom: 20 })
.accessibilityLevel('yes')
ForEach(this.checklistItems, (category, categoryIndex) => {
Column() {
Text(category.category)
.fontSize(18)
.fontWeight(FontWeight.Medium)
.margin({ bottom: 12 })
.accessibilityText(`检查类别:${category.category}`)
ForEach(category.items, (item, itemIndex) => {
let globalIndex = categoryIndex * 4 + itemIndex
Row() {
Checkbox()
.select(this.checkedItems[globalIndex])
.onChange((checked: boolean) => {
this.checkedItems[globalIndex] = checked
})
.accessibilityText(`检查项复选框`)
Text(item)
.fontSize(14)
.margin({ left: 12 })
.layoutWeight(1)
}
.width('100%')
.padding({ vertical: 8 })
.accessibilityGroup(true)
.accessibilityText(
`检查项:${item},${this.checkedItems[globalIndex] ? '已完成' : '未完成'}`
)
})
}
.alignItems(HorizontalAlign.Start)
.margin({ bottom: 24 })
.width('100%')
})
// 完成度统计
let completedCount = this.checkedItems.filter(item => item).length
let totalCount = this.checkedItems.length
let completionRate = Math.round((completedCount / totalCount) * 100)
Column() {
Text('完成度统计')
.fontSize(16)
.fontWeight(FontWeight.Medium)
.margin({ bottom: 8 })
Progress({ value: completedCount, total: totalCount, type: ProgressType.Linear })
.width('100%')
.height(8)
.accessibilityText('检查清单完成度进度条')
.accessibilityDescription(`已完成${completedCount}项,共${totalCount}项,完成度${completionRate}%`)
Text(`${completedCount}/${totalCount} (${completionRate}%)`)
.fontSize(14)
.fontColor('#666666')
.margin({ top: 8 })
}
.width('100%')
.padding(16)
.backgroundColor('#F8F9FA')
.borderRadius(8)
.margin({ top: 20 })
}
.padding(20)
}
.width('100%')
.height('100%')
}
}
2.4 总结
无障碍开发不是额外的负担,而是创造更好用户体验的机会。通过遵循以下原则,我们可以构建真正包容的应用:
核心要点:
- 从设计阶段就考虑无障碍:不要等到开发完成后再添加无障碍支持
- 测试是关键:使用真实的辅助技术进行测试
- 用户反馈很重要:倾听有障碍用户的真实需求
- 持续改进:无障碍是一个持续优化的过程
技术实现要点:
- 合理使用accessibilityText、accessibilityDescription等属性
- 正确设置accessibilityGroup和accessibilityLevel
- 提供清晰的错误提示和操作反馈
- 确保键盘导航和屏幕阅读器兼容性
通过本指南的学习和实践,相信您已经掌握了鸿蒙无障碍开发的核心技能。让我们一起努力,创造一个更加包容和友好的数字世界!
00
- 0回答
- 0粉丝
- 0关注