Flutter 中的可访问性
学习如何让你的 Flutter 应用更具可访问性。
可访问性 指的是让我们的应用程序尽可能被更多人使用。我们通常认为可访问性是针对残障人士的,但实际上,使应用程序具备可访问性也对其他群体有益,例如使用不同语言的人、从右向左阅读的人、网络连接较慢的人,或有轻微功能障碍的人。
我们不应排除任何能力、处境或文化背景的人使用或有效使用我们的应用程序。
在本课程中,我们将学习如何让应用程序对不同用户群体更具可访问性,无论他们的能力或处境如何。
我们将通过以下方式实现这一目标:
- 了解不同类型的残疾
- 使用工具扫描应用程序以发现可访问性问题
- 为应用程序的语义添加标签
- 让应用程序对神经多样性用户更加友好
残疾类型
有残疾的人和没有残疾的人一样多样化,他们的能力也各不相同。残疾主要分为四类:
- 运动障碍:影响运动能力
- 视觉障碍:影响视觉能力
- 神经多样性障碍:影响感知和专注力
- 听觉障碍
下表列出了不同类型的残疾、人们在使用我们的应用程序时可能遇到的挑战,以及我们可以通过提升应用可访问性来帮助他们的方式。
| 残疾类型 | 面临的挑战 | 如何帮助 |
|---|---|---|
| 运动障碍 | 需要更大的点击/触摸区域;需要特殊硬件;需要语音控制软件 | 在游戏中提供不同难度等级;支持键盘导航;支持语音导航;使用自适应 UI 组件;启用文本自动补全 |
| 视觉障碍 | 失明;严重视力受损;中度视力受损;轻度视力受损;色盲 | 使用大字号;支持屏幕阅读器;高对比度内容;同时使用颜色和图标传递信息;在应用中提供颜色调整选项 |
| 神经多样性障碍 | 注意力难以集中;学习困难;自闭症;阅读障碍;注意力缺陷多动障碍(ADHD) | 采用极简设计;提供语音朗读和字幕;支持拼写检查、自动纠正和句子首字母大写;允许保存设置和密码;通过提示引导用户,但不过度干扰 |
| 听觉障碍 | 空气传导或骨传导听力阈值较高;词语识别测试中无法重复单词 | 提供字幕;在游戏内加入视觉信号;通知时使用震动 |
No configuration found for ID: b67d07182bacc1116c071e5c148f5258f0b4b45c8c8ddbeae2cf901f3647b108
使用可访问性扫描工具检测应用问题
为了发现应用程序中的可访问性问题,请在 Android 模拟器或实体设备上运行应用,并使用 可访问性扫描工具 Android 应用。
上述示例应用包含多个可访问性问题,如下图所示。
发现的所有可访问性问题
请按照以下步骤修复可访问性扫描工具识别出的问题:
-
在
home_page.dart文件中,找到// TODO-1: 为 Scaffold 添加语义标签(第 36 行)。将整个Scaffold包裹在Semantics小部件中,并为其添加标签,代码如下:1 2Widget build(BuildContext context) { 3 return Semantics( 4 label: "主页", 5 child: Scaffold( 6 ... 7 ), 8 ); 9}上述更改将使屏幕阅读器能够为视障及其他障碍用户朗读“主页”标签。
-
在
home_page.dart文件中,找到// TODO-2: 提高文字与背景的对比度。为了使TextField的hintText更清晰可见,将提示文字颜色改为更深的灰色,以增强与背景的对比。替换原代码如下:1hintStyle: TextStyle(color: Colors.grey[600]), -
为解决最后一个问题,进入
message_widget.dart文件的第 35 行,将InkWell替换为以下代码:1InkWell( 2 child: ConstrainedBox( 3 constraints: const BoxConstraints( 4 minHeight: kMinInteractiveDimension, 5 minWidth: kMinInteractiveDimension, 6 ), 7 child: Align( 8 alignment: Alignment.centerLeft, 9 // TODO-9: 为消息正文文本排除语义 10 child: Text( 11 message['author']!, 12 style: const TextStyle(fontWeight: FontWeight.bold), 13 ), 14 ), 15 ), 16 onTap: () { 17 Navigator.of(context).push(MaterialPageRoute( 18 builder: (_) => 19 AuthorPage(author: message['author']!))); 20 }, 21),由于
InkWell的点击区域与其子组件大小一致,上述代码通过使用ConstrainedBox定义一个更大的容器包裹Text组件,从而扩大了点击区域,使文本更易于点击。
为应用程序标注语义信息
为了发现并可视化应用程序中各组件的语义信息,可以将某个组件包裹在 SemanticsDebugger 中。将 HomePage 的 Scaffold 包裹在 SemanticsDebugger 中后,将得到如下图像:
上述标记的区域是屏幕上需要添加语义标签的部分,以便屏幕阅读器帮助残障用户识别界面组件。请按以下步骤为这些组件添加语义标签:
-
在
home_page.dart文件中,将// TODO-4: 为菜单图标添加语义标签替换为以下代码:1semanticLabel: "菜单", -
将
// TODO-5: 为“附加媒体”图标添加语义标签替换为:1semanticLabel: "附加媒体", -
将
// TODO-6: 为“发送”按钮添加语义标签替换为:1semanticLabel: "发送",第 1、2、3 步分别为“菜单”、“附加媒体”和“发送”图标添加了语义标签,使屏幕阅读器能将其读作“菜单按钮”、“附加媒体按钮”和“发送按钮”。
注意:由于“菜单”按钮处于禁用状态,屏幕阅读器会读作“菜单按钮已禁用”。
接下来,进入
message_widget.dart文件,完成第 4 至第 8 步。 -
在
message_widget.dart文件中,找到// TODO-7: 为 MessageWidget 添加语义标签,并将Padding组件包裹在Semantics小部件中。修改后的代码如下:1return Semantics( 2 label: "来自 ${message['author']} 的消息:${message['body']},时间:${DateFormat.Hm().format(DateTime.parse(message['time']!))}。", 3 child: Padding( 4 ... 5 ), 6);上述代码为整个消息组件添加了语义标签,朗读内容包括发信人姓名、消息内容和发送时间。
-
找到
// TODO-8: 为作者头像文本排除语义,将Text组件替换为以下代码:1ExcludeSemantics(child: Text(message['author']!.substring(0, 1))), -
找到
// TODO-9: 为消息正文文本排除语义,将作者名称的Text组件替换为以下代码:1ExcludeSemantics( 2 child: Text( 3 message['author']!, 4 style: const TextStyle(fontWeight: FontWeight.bold), 5 ), 6), -
找到
// TODO-10: 为消息时间文本排除语义,将时间戳Text组件替换为以下代码:1ExcludeSemantics( 2 child: Text( 3 DateFormat.Hm().format(DateTime.parse(message['time']!)), 4 style: Theme.of(context).textTheme.caption, 5 ), 6), -
找到
// TODO-11: 为消息正文文本排除语义,将消息内容的Text组件替换为:1ExcludeSemantics(child: Text(message['body']!)),第 5 至第 8 步使用
ExcludeSemantics小部件排除了作者、消息和时间戳Text组件的语义。之所以这样做,是因为我们在第 4 步已经通过整体MessageWidget的语义标签概括了全部信息,因此无需重复朗读这些文本内容。屏幕阅读器默认会朗读
Text组件的内容。如果不排除这些组件的语义,屏幕阅读器会重复朗读内容,造成冗余。为测试语义功能,可下载并在启用了 TalkBack 功能的 Android 设备或模拟器上运行应用。iOS 也支持通过 VoiceOver 功能实现屏幕朗读。
让应用对神经多样性用户更友好
为了让神经多样性用户在使用应用时体验更舒适,可以在适用场景中启用自动建议、自动补全和自动首字母大写功能。
在 home_page.dart 文件中,找到 // TODO-12: 让应用对神经多样性用户更友好,并替换为以下属性添加到 TextField 中:
1autocorrect: true, 2enableSuggestions: true, 3textCapitalization: TextCapitalization.sentences,
启用这些属性后,神经多样性用户在撰写消息时将获得所需的支持。
No configuration found for ID: 9d96fa551930d4d42fc759ea6bec34a72a9b312dfc91c582c9ae4f11522b9c3a