mcpField
F
flutter-improving-accessibility

flutter-improving-accessibility

github.com/thesimpleapp设计与 UI059 次安装11 周前更新
代码仓库

关于 flutter-improving-accessibility

面向 Flutter 开发的无障碍检查与改进技能,强调复用 Material 和 Cupertino 的内置语义,并覆盖读屏、文字缩放、对比度及测试。

主要功能

  1. 01复用现有无障碍约定
  2. 02为自定义控件添加语义
  3. 03处理文字缩放与对比度
  4. 04检查触控区域和遍历顺序
  5. 05运行无障碍规范测试

适用场景

  1. 01修复 Flutter 屏幕的无障碍问题
  2. 02标注自绘或仅图标控件
  3. 03检查大字号下的布局
  4. 04测试语义节点和触控区域

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/thesimpleapp/flutter-app-skills --skill flutter-improving-accessibility

安装后在 Claude Code 中输入/flutter-improving-accessibility确认已加载。

附带文件references/recipes.md

详细介绍

Flutter 无障碍功能

优先使用内置的 Material 和 Cupertino 组件——它们已能为 TalkBack / VoiceOver / JAWS / NVDA 生成语义树。仅在自定义绘制或手势组件上添加 Semantics(或 Semantics.fromProperties)。

如果此代码库已有 Semantics 辅助工具、无障碍测试框架、带标签控件的约定,或带有 semanticLabel 的可按压组件,请扩展现有方案。不要再套一层 Semantics(label:),重复显示文本。

官方文档:无障碍功能、辅助技术、UI 设计与样式、无障碍测试。

参考资料

实现自定义控件、合并/排除/阻断语义、连接 MediaQuery 或编写指南测试时,请阅读 references/recipes.md。

先检查

在代码库中搜索 Semantics(、MergeSemantics、ExcludeSemantics、BlockSemantics、semanticLabel、excludeFromSemantics、tooltip:、meetsGuideline(、ensureSemantics(、highContrastTheme、accessibleNavigationOf、textScalerOf。打开附近的生产环境页面,并沿用其模式。

硬性规则

语义

  • 自定义绘制 / GestureDetector / InkWell:设置 label、角色标志(button、slider、textField)、tooltip / hint 和 onTap,使节点被播报为控件。
  • 角色是一个标志:Semantics(button: true, label: 'Submit')。不要在标签中写入“按钮”。label / hint / tooltip 需要环境中存在 Directionality,或显式设置 textDirection。瞬时状态:liveRegion: true。
  • container: true 会引入一个 SemanticsNode——用于建立独立的 TalkBack/VoiceOver 焦点停留点。

合并、排除、阻挡

  • 一个控件及其可见的 Text:将两者包在 MergeSemantics 中。绝不要合并两个复选框、两个可点击的子项,或 selected / checked 值冲突的节点。
  • ExcludeSemantics 仅用于装饰性或冗余的后代节点。Semantics(excludeSemantics: true) 会用此节点替换所有子节点语义。隐藏绘制在模态框或 Drawer 后面的 Widget 时,应在叠层上使用 BlockSemantics,而不是在叠层子项上使用 ExcludeSemantics。

点击目标和名称

  • 每个可点击目标至少为 48×48 逻辑像素(kMinInteractiveDimension)。Cupertino 的下限为 kMinInteractiveDimensionCupertino(44.0)。优先使用 IconButton,确保点击区域始终为 48,不受 iconSize 影响。不要传入会将尺寸缩小至 48 以下的 IconButton.constraints 或紧凑型 VisualDensity。
  • 仅含图标的 IconButton / FAB:tooltip 应为简短的操作名称。自定义图标点击项:Semantics(label: ..., button: true)。装饰性 Icon:semanticLabel。有意义的 Image:semanticLabel。装饰性 Image:excludeFromSemantics: true。

文本缩放

  • 使用 MediaQuery.textScalerOf(context) 读取,并用 textScaler.scale(fontSize) 计算尺寸。绝不要使用 textScaleFactor——它已弃用,且无法表示 Android 14 的非线性缩放。
  • Text 已遵循操作系统的字号设置。在小屏设备上使用操作系统的最大字号测试每个屏幕。仅使用 MediaQuery.withNoTextScaling 阻止图标字体缩放。绝不要在 MaterialApp 根节点禁用缩放。

MediaQuery 标志

  • 调用 MediaQuery.accessibleNavigationOf、highContrastOf、disableAnimationsOf 和 textScalerOf,不要调用 MediaQuery.of。
  • 当 accessibleNavigationOf 为 true 时,禁用超时,或提高超时时长下限。
  • 当 highContrastOf 为 true 时,设置 MaterialApp.highContrastTheme 和 highContrastDarkTheme。在子树外层使用 MediaQuery(data: copyWith(highContrast: true)) 并不会切换这些主题。
  • disableAnimationsOf 对应 Android 的移除动画。AnimationController 已遵循 SemanticsBinding.disableAnimations。自定义显式动画仍必须读取 disableAnimationsOf。iOS 的减少动态效果设置对应 AccessibilityFeatures.reduceMotion,不会设置 disableAnimations。

遍历顺序

  • TalkBack/VoiceOver 的同级节点顺序:Semantics(sortKey: OrdinalSortKey(n))。键盘/Tab 顺序属于不同的树:使用 FocusTraversalGroup / FocusTraversalOrder。不要用 sortKey 修正 Tab 顺序。

对比度

  • 文本/控件与背景之间的对比度至少为 4.5:1(禁用状态除外)。大号文本(常规字重 18pt 及以上,或粗体 14pt 及以上)为 3.0:1。不要仅靠颜色传达状态。

测试和设备

  • 先调用 tester.ensureSemantics(),再调用 expectLater(tester, meetsGuideline(...)),最后调用 handle.dispose()。指南:androidTapTargetGuideline、iOSTapTargetGuideline、labeledTapTargetGuideline、textContrastGuideline。
  • 使用 TalkBack 和 VoiceOver 遍历每个有改动的屏幕。Flutter Web:在 main() 中调用 SemanticsBinding.instance.ensureSemantics()。使用 showSemanticsDebugger 或 flutter run -d chrome --profile --dart-define=FLUTTER_WEB_DEBUG_SHOW_SEMANTICS=true 进行调试。
示例
Semantics(
  button: true,
  label: 'Submit',
  hint: 'Sends the form',
  onTap: onSubmit,
  child: CustomPaint(painter: SubmitPainter()),
)

IconButton(
  tooltip: 'Search',
  icon: const Icon(Icons.search),
  onPressed: onSearch,
)

final TextScaler scaler = MediaQuery.textScalerOf(context);
final bool screenReader = MediaQuery.accessibleNavigationOf(context);

不要

  • 不要在已有语义的 Material/Cupertino 控件外包一层 Semantics(label:),重复显示的文本。
  • 不要在可交互内容外包一层 ExcludeSemantics,也不要用它代替 BlockSemantics 来隐藏对话框后面的控件。
  • 不要对两个复选框或两个可点击的子项使用 MergeSemantics。
  • 不要使用没有 tooltip、没有 Semantics.label 且没有 Icon.semanticLabel 的纯图标控件。
  • 不要使用 MediaQuery.of(context).textScaleFactor 或 Text(textScaleFactor:)。
  • 不要在应用根节点禁用文本缩放,也不要将 IconButton 缩小到 48 以下。
  • 不要假设 MediaQuery(data: copyWith(disableAnimations: true)) 会停止 AnimationController。
  • 不要将角色写入标签(label: 'Submit button')。
  • 不要使用 Semantics.sortKey 修复键盘 Tab 顺序。

修复屏幕

  1. 检测现有的无障碍约定并沿用。
  2. 除非是自定义绘制的控件或纯图标控件,否则不要为 Material/Cupertino 内置控件添加标签。
  3. 为每个纯图标控件提供 tooltip 或 Semantics(label:, button: true)。将触控目标填充到 48,而不是图标本身。
  4. 读取 MediaQuery.*Of。兼顾最大字号、高对比度主题、屏幕阅读器超时设置和减少动态效果。
  5. 添加指南测试,并使用 TalkBack 和 VoiceOver 逐项检查。

验证

  1. 在你修改的屏幕上调用 tester.ensureSemantics()、进行四项 meetsGuideline 检查,并调用 handle.dispose()。
  2. 在小屏设备上,布局仍能适配操作系统的最大字号。
  3. 对比度 ≥ 4.5:1(大号文本为 3:1);状态不能仅通过颜色区分。
  4. 在 TalkBack 和 VoiceOver 中,每个控件都有清晰易懂的名称和正确的角色。
  5. Web:从 main() 启用语义。

评论

支持 Markdown