无障碍技术里,屏幕朗读服务的是视障用户这个相对小众的群体,但"关怀模式"(也叫长辈模式、长辈版、关爱版、大字版)服务的却是海量的老年用户。这是无障碍里离商业价值最近的一块,也是很多互联网产品在做适老化改造时的重点。鸿蒙从 API 26 开始,把关怀模式的接入能力系统化了。这篇讲清楚三种接入思路,以及各自的适用场景。

一、关怀模式要解决什么问题

老年人用手机的核心痛点很集中:字小看不清、图标小点不准、流程复杂记不住。关怀模式就是针对这些痛点做的大字号、大图标、高对比度、简化流程的界面形态。

和屏幕朗读不同,关怀模式不改变交互范式(还是靠"看+点"),而是放大视觉和交互的容错空间。所以它不需要 TTS 引擎,实现成本也低得多——本质就是一套"放大版"的 UI 配置。

鸿蒙把关怀模式做成了一个系统级的能力:设备"设置 → 关怀和无障碍 → 关怀模式"里有个总开关,还有"应用管理",可以针对每个应用单独控制关怀模式。

二、三种接入思路总览

官方文档提供了三种接入思路,对应不同的产品诉求:

思路适用场景核心接口/配置
1. 声明接入,独立控制应用有自己的关怀模式开关,且希望暴露到系统设置里统一管理module.json5metadata 声明
2. 应用内开关与系统同步应用内有独立开关,但要和系统"应用管理"页保持双向同步getSeniorModeStateForSelf / setSeniorModeStateForSelf
3. 跟随系统,不自带开关应用没有独立开关,完全跟随系统关怀模式isSeniorModeEnabled / onSeniorModeStateChange

先想清楚你属于哪种,再选接口,能少走很多弯路。

三、思路一:声明接入,让应用出现在系统设置里

如果你的应用已经自己实现了关怀模式(有独立的开关和 UI),想把它接入系统,方便用户在"设置 → 关怀模式 → 应用管理"里统一查看和切换,那只需要在 module.json5 里加一段 metadata 声明。

从 API 26.0.0 开始,在对应 module 的 metadata 里声明:

{
  "module": {
    "metadata": [{
      "name": "senior_mode",
      "value": "independent_control"
    }]
  }
}

声明之后,用户的设备"设置 → 关怀和无障碍 → 关怀模式 → 应用管理"里就会出现你的应用,用户可以自由切换该应用的关怀模式开关。

这里有个重要的联动行为要记住:

  • 用户在设置里关闭系统总开关时,应用内关怀模式也会随之关闭
  • 重新开启系统总开关,原先被关闭的应用会同步开启

这个"总开关联动"的设计,保证了用户的操作是一致的——关了系统关怀,所有应用都跟着关,不会出现"系统关了某个应用还开着"的割裂状态。

建议:声明写在有关怀模式功能的那个 module 下,而不是随便哪个 module。

四、思路二:应用内开关与系统双向同步

思路一只是"声明了、能出现",但应用内开关和系统设置里的开关还是两套状态。用户在系统里切了开关,应用内不会自动知道;反之亦然。要打通这两者,需要思路二的接口。

4.1 三个核心接口

接口作用
getSeniorModeStateForSelf()查询系统"应用管理"页里本应用的关怀模式开关状态
setSeniorModeStateForSelf(state)设置系统"应用管理"页里本应用的开关状态
onSeniorModeStateChangeForSelf(cb)监听系统"应用管理"页里的开关变化
offSeniorModeStateChangeForSelf(cb)取消监听

注意 ForSelf 这个后缀——这些接口操作的都是当前应用自身在系统设置里的开关,而不是系统总开关。总开关是另一套接口(思路三)。

4.2 完整代码示例

完整的同步逻辑分三步:启动时查询、监听变化、本地切换时上报。

import accessibility from '@ohos.accessibility'

@Entry
@Component
struct Index {
  @State state: boolean = false;

  callBack = (data: boolean) => {
    console.info(`data: ` + data);
    this.state = data;
    // 应用内关怀模式开启/关闭的 UI 刷新
  }

  async aboutToAppear(): Promise<void> {
    // 1. 注册监听
    accessibility.onSeniorModeStateChangeForSelf(this.callBack);
    // 2. 启动时查询当前状态
    this.state = await accessibility.getSeniorModeStateForSelf();
  }

  aboutToDisappear(): void {
    // 3. 取消监听
    accessibility.offSeniorModeStateChangeForSelf(this.callBack);
  }

  build() {
    Column() {
      Row() {
        Text(`senior mode: ${this.state ? 'open' : 'closed'}`)
          .fontSize(18)
        Toggle({ type: ToggleType.Switch, isOn: this.state })
          .onChange(async (isOn: boolean) => {
            if (isOn !== this.state) {
              this.state = isOn;
              // 应用内切换时,上报给系统保持同步
              await accessibility.setSeniorModeStateForSelf(isOn);
            }
          })
      }
    }
    .height('100%')
    .width('100%')
  }
}

这段代码体现了三个时机的处理:

  1. aboutToAppear:注册监听 + 查询初始状态(保证一进来就和系统对齐)
  2. 监听回调:系统设置变化时,同步刷新应用内 UI
  3. onChange:应用内用户手动切换时,调用 setSeniorModeStateForSelf 上报系统

监听的生命周期管理也很规范:aboutToAppear 注册、aboutToDisappear 取消,避免内存泄漏。

五、思路三:跟随系统,不自带开关

如果你的应用不想自己维护关怀模式的状态,完全跟随系统总开关走,那更简单——用系统总开关的查询和监听接口。

5.1 核心接口

接口作用
isSeniorModeEnabled()查询系统关怀模式总开关状态
onSeniorModeStateChange(cb)监听系统总开关变化
offSeniorModeStateChange(cb)取消监听

注意和思路二的区别:这里没有 ForSelf 后缀,操作的是系统全局关怀模式,不是"本应用"。

5.2 代码示例:监听系统总开关

import { accessibility } from '@kit.AccessibilityKit';

@Entry
@Component
struct SeniorModeDemo {
  callback = (data: boolean) => {
    console.info(`senior mode changed: ${JSON.stringify(data)}`);
    // data 为 true 表示系统关怀模式已打开
  }

  aboutToAppear(): void {
    accessibility.onSeniorModeStateChange(this.callback);
  }

  aboutToDisappear(): void {
    accessibility.offSeniorModeStateChange(this.callback);
  }

  build() {
    // 自行展示 UX 界面
  }
}

5.3 代码示例:查询系统总开关

import { accessibility } from '@kit.AccessibilityKit';
import { BusinessError } from '@kit.BasicServicesKit';

@Entry
@Component
struct SeniorModeQuery {
  aboutToAppear(): void {
    accessibility.isSeniorModeEnabled()
      .then((data: boolean) => {
        console.info(`isSeniorModeEnabled: ${JSON.stringify(data)}`);
      })
      .catch((err: BusinessError) => {
        console.error(`failed, code: ${err.code}, message: ${err.message}`);
      });
  }

  build() {
    // 自行展示 UX 界面
  }
}

5.4 导入方式的一个细节

思路三的示例用的是 import { accessibility } from '@kit.AccessibilityKit',而思路二用的是 import accessibility from '@ohos.accessibility'。两种导入方式都存在于官方文档里。实际项目里,建议统一用 Kit 化的导入方式 @kit.AccessibilityKit,这是新版本推荐的风格,也和前几篇文章的 accessibility 用法保持一致。

六、怎么选:一张决策图

三种思路的选择,本质是回答两个问题:

  1. 我的应用有独立的关怀模式开关吗?

    • 有 → 思路二(或思路一 + 思路二组合)
    • 没有 → 思路三
  2. 我希望关怀模式能在系统的"应用管理"里被用户看到和控制吗?

    • 希望 → 先做思路一的 metadata 声明,再做思路二的同步
    • 无所谓,跟系统走就行 → 思路三

大多数做了适老化改造的成熟应用,最终都会走到"思路一 + 思路二"的组合:声明接入、双向同步。因为这样才能既在系统里统一管理,又保证应用内外的状态一致。

七、几个开发注意事项

7.1 版本门槛

这一整套关怀模式接口都是 API 26.0.0 才有的。如果你的目标 SDK 低于 26,这些接口不可用。动手前先确认工程的 API 版本。

7.2 监听要及时注销

onSeniorModeStateChangeForSelfonSeniorModeStateChange 注册的监听,一定要在组件销毁时(aboutToDisappear)注销。否则会造成内存泄漏和重复回调。

7.3 状态初始化的时机

应用冷启动时,aboutToAppear 里先查询一次状态、再注册监听,能避免"启动瞬间用了过期状态"的问题。查询和监听谁先谁后,取决于你能否容忍初始态的短暂空窗。

7.4 UI 刷新要解耦

关怀模式切换会带来大面积的 UI 变化(字号、间距、布局)。建议用一个全局状态(如 AppStorage)承载关怀模式开关,各组件订阅这个状态来刷新,而不是每个组件都自己去调 accessibility 接口。这样状态来源单一,刷新也好控制。

八、总结一下下

关怀模式是无障碍里最"接地气"的一环。三种思路可以浓缩成一句话:

  • 有自己的开关 + 想进系统设置:声明 metadata + 用 ForSelf 系列接口做双向同步
  • 没自己的开关:用 isSeniorModeEnabled / onSeniorModeStateChange 跟随系统总开关

记住接口的命名规律就不会混:带 ForSelf 的是"本应用"的开关,不带的是"系统总开关"。

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐