鸿蒙多功能工具箱开发实战(二十六)-无障碍访问支持

前言

无障碍访问是应用包容性的重要体现,帮助视障、听障等用户群体更好地使用应用。本文将讲解HarmonyOS无障碍功能的实现。

一、无障碍基础

1.1 无障碍属性

ArkUI组件支持以下无障碍属性:

属性 说明
accessibilityText 无障碍文本
accessibilityDescription 无障碍描述
accessibilityLevel 无障碍等级
accessibilityGroup 是否为无障碍组

1.2 基础用法

@Entry
@Component
struct AccessiblePage {
  build() {
    Column() {
      // 图片添加无障碍描述
      Image($r('app.media.icon'))
        .width(48)
        .height(48)
        .accessibilityText('应用图标')
        .accessibilityDescription('多功能工具箱应用图标')

      // 按钮添加无障碍支持
      Button('计算')
        .accessibilityText('计算按钮')
        .accessibilityDescription('点击此按钮开始计算')
        .onClick(() => {
          // 计算逻辑
        })

      // 输入框添加提示
      TextInput({ placeholder: '请输入金额' })
        .accessibilityText('金额输入框')
        .accessibilityDescription('请输入需要计算的金额')
    }
  }
}

二、无障碍组件封装

2.1 无障碍按钮

/**
 * 无障碍按钮组件
 */
@Component
export struct AccessibleButton {
  @Prop text: string = ''
  @Prop accessibilityLabel: string = ''
  @Prop accessibilityHint: string = ''
  @Prop disabled: boolean = false
  onButtonClick: () => void = () => {}

  build() {
    Button(this.text)
      .enabled(!this.disabled)
      .accessibilityText(this.accessibilityLabel || this.text)
      .accessibilityDescription(this.accessibilityHint)
      .accessibilityLevel(this.disabled ? 'no' : 'yes')
      .onClick(() => {
        if (!this.disabled) {
          this.onButtonClick()
        }
      })
  }
}

2.2 无障碍图片

/**
 * 无障碍图片组件
 */
@Component
export struct AccessibleImage {
  @Prop src: Resource | string = ''
  @Prop alt: string = ''
  @Prop description: string = ''
  @Prop width: number = 100
  @Prop height: number = 100

  build() {
    Image(this.src)
      .width(this.width)
      .height(this.height)
      .alt(this.alt)  // 图片加载失败时的替代文本
      .accessibilityText(this.alt)
      .accessibilityDescription(this.description)
  }
}

2.3 无障碍列表项

/**
 * 无障碍列表项
 */
@Component
export struct AccessibleListItem {
  @Prop title: string = ''
  @Prop subtitle: string = ''
  @Prop icon: string = ''
  onItemClick: () => void = () => {}

  build() {
    Row() {
      if (this.icon) {
        Text(this.icon)
          .fontSize(24)
          .margin({ right: 12 })
      }

      Column() {
        Text(this.title)
          .fontSize(16)

        if (this.subtitle) {
          Text(this.subtitle)
            .fontSize(12)
            .fontColor('#999999')
            .margin({ top: 4 })
        }
      }
      .layoutWeight(1)
      .alignItems(HorizontalAlign.Start)

      Text('›')
        .fontSize(20)
        .fontColor('#999999')
    }
    .width('100%')
    .height(56)
    .padding({ left: 16, right: 16 })
    // 无障碍支持
    .accessibilityGroup(true)
    .accessibilityText(`${this.title}${this.subtitle}`)
    .accessibilityDescription('双击进入')
    .onClick(() => {
      this.onItemClick()
    })
  }
}

三、屏幕阅读器支持

3.1 焦点管理

@Entry
@Component
struct FocusManagementPage {
  @State focusIndex: number = 0

  build() {
    Column() {
      // 焦点顺序控制
      Button('按钮1')
        .tabIndex(1)
        .defaultFocus(true)  // 默认焦点
        .onClick(() => {})

      Button('按钮2')
        .tabIndex(2)
        .onClick(() => {})

      Button('按钮3')
        .tabIndex(3)
        .onClick(() => {})

      // 跳过装饰性元素
      Image($r('app.media.decoration'))
        .accessibilityLevel('no')  // 屏幕阅读器跳过
    }
  }
}

3.2 实时区域通知

/**
 * 无障碍通知组件
 */
@Component
export struct AccessibleAnnouncement {
  @State message: string = ''
  @State isVisible: boolean = false

  /**
   * 显示通知
   */
  show(msg: string): void {
    this.message = msg
    this.isVisible = true

    // 3秒后自动隐藏
    setTimeout(() => {
      this.isVisible = false
    }, 3000)
  }

  build() {
    if (this.isVisible) {
      Text(this.message)
        .width('100%')
        .height(48)
        .backgroundColor('#333333')
        .fontColor('#FFFFFF')
        .textAlign(TextAlign.Center)
        // 实时区域,屏幕阅读器会自动播报
        .accessibilityLiveRegion('polite')
    }
  }
}

四、最佳实践

4.1 语义化结构

@Entry
@Component
struct SemanticPage {
  build() {
    Column() {
      // 标题区域
      Column() {
        Text('计算结果')
          .fontSize(24)
          .fontWeight(FontWeight.Bold)
          .accessibilityRole('header')
      }
      .accessibilityGroup(true)

      // 内容区域
      Column() {
        Text('税后工资: 12000元')
          .accessibilityRole('text')

        Text('应纳税额: 500元')
          .accessibilityRole('text')
      }
      .accessibilityGroup(true)
      .accessibilityText('计算结果详情')

      // 操作区域
      Row() {
        Button('保存')
          .accessibilityRole('button')

        Button('分享')
          .accessibilityRole('button')
      }
      .accessibilityGroup(true)
      .accessibilityText('操作按钮')
    }
  }
}

4.2 状态变化通知

@Entry
@Component
struct StateAnnouncementPage {
  @State loading: boolean = false
  @State result: string = ''

  async calculate() {
    this.loading = true
    // 通知用户正在加载
    this.announce('正在计算,请稍候')

    await this.doCalculate()

    this.loading = false
    this.result = '计算完成'
    // 通知用户结果
    this.announce('计算完成,结果是' + this.result)
  }

  private announce(message: string) {
    // 使用无障碍API播报
    // 实际实现需要调用系统无障碍服务
  }

  build() {
    Column() {
      if (this.loading) {
        LoadingProgress()
          .accessibilityText('正在计算')
      } else {
        Text(this.result)
          .accessibilityText('计算结果: ' + this.result)
      }
    }
  }
}

五、高级无障碍功能

5.1 无障碍流程图

用户操作

无障碍服务检测

是否开启?

播报内容

正常显示

用户理解

5.2 手势支持

@Component
struct AccessibleGesture {
  onDoubleTap: () => void
  
  build() {
    Text('双击激活')
      .accessibilityText('双击此按钮激活功能')
      .gesture(
        TapGesture({ count: 2 })
          .onAction(() => this.onDoubleTap())
      )
  }
}

5.3 键盘导航

@Component
struct KeyboardNavigation {
  @State focusedIndex: number = 0
  
  build() {
    Column() {
      ForEach(['选项1', '选项2', '选项3'], (item: string, index: number) => {
        Text(item)
          .focusable(true)
          .defaultFocus(index === this.focusedIndex)
          .onKeyEvent((event) => {
            if (event.keyCode === KeyCode.KEYCODE_DPAD_DOWN) {
              this.focusedIndex = Math.min(this.focusedIndex + 1, 2)
            }
          })
      })
    }
  }
}

图 1 无障碍设置页面选择是否启用
在这里插入图片描述

六、小结

本文详细讲解了无障碍支持:

  1. ✅ 无障碍属性使用
  2. ✅ 无障碍组件封装
  3. ✅ 焦点管理
  4. ✅ 屏幕阅读器支持
  5. ✅ 语义化结构
  6. ✅ 手势支持
  7. ✅ 键盘导航

系列文章导航
下期预告:鸿蒙多功能工具箱开发实战(二十七)-安全加固与数据保护

Logo

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

更多推荐