flutter-accessibility-audit
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseImplementing Flutter Accessibility
Flutter无障碍功能实现
Contents
目录
Managing Semantics
语义管理
Rely on Flutter's standard widgets (e.g., , ) for automatic semantic role assignment whenever possible. When building custom components or overriding default behaviors, explicitly define the UI element's purpose using the widget.
TabBarMenuAnchorSemantics- Wrap custom UI components in a widget.
Semantics - Assign the appropriate enum value to the
SemanticsRoleproperty to define the element's purpose (e.g., button, list, heading).role - If building for Flutter Web, note that Flutter translates these roles into corresponding ARIA roles in the HTML DOM.
- Enable web accessibility explicitly. It is disabled by default for performance. Either instruct users to press the invisible button, or force it programmatically in your
aria-label="Enable accessibility"function.main()
尽可能依赖Flutter的标准组件(如、)来自动分配语义角色。在构建自定义组件或覆盖默认行为时,使用组件明确定义UI元素的用途。
TabBarMenuAnchorSemantics- 将自定义UI组件包裹在组件中。
Semantics - 为属性分配合适的
role枚举值,以定义元素用途(如按钮、列表、标题)。SemanticsRole - 如果针对Flutter Web开发,请注意Flutter会将这些角色转换为HTML DOM中对应的ARIA角色。
- 显式启用Web无障碍功能,它默认处于禁用状态以提升性能。可以指导用户点击隐藏的按钮,或在
aria-label="Enable accessibility"函数中通过代码强制启用。main()
Auditing Accessibility
无障碍审计
Implement the following workflows to verify that your application meets accessibility standards.
实施以下工作流,验证应用是否符合无障碍标准。
Task Progress: Platform-Specific Scanning
任务进度:平台专属扫描
Copy this checklist to track your manual auditing progress across target platforms:
- If testing on Android:
- Install the Accessibility Scanner from Google Play.
- Enable it via Settings > Accessibility > Accessibility Scanner > On.
- Tap the Accessibility Scanner checkmark icon over your running app to initiate the scan.
- If testing on iOS:
- Open the folder in Xcode and run the app on a Simulator.
ios - Navigate to Xcode > Open Developer Tools > Accessibility Inspector.
- Select Inspection > Enable Point to Inspect and click UI elements to verify attributes.
- Select Audit > Run Audit to generate an issue report.
- Open the
- If testing on Web:
- Open Chrome DevTools.
- Inspect the HTML tree under the node.
semantics host - Navigate to the Elements tab and open the Accessibility sub-tab to inspect exported ARIA data.
- Visualize semantic nodes by running the app with: .
flutter run -d chrome --profile --dart-define=FLUTTER_WEB_DEBUG_SHOW_SEMANTICS=true
复制此检查清单,跟踪各目标平台的手动审计进度:
- Android测试:
- 从Google Play安装Accessibility Scanner。
- 通过设置 > 无障碍 > Accessibility Scanner > 开启启用它。
- 在运行的应用上方点击Accessibility Scanner的对勾图标,启动扫描。
- iOS测试:
- 在Xcode中打开文件夹,在模拟器上运行应用。
ios - 导航至Xcode > 打开开发者工具 > Accessibility Inspector。
- 选择检查 > 启用点选检查,点击UI元素验证属性。
- 选择审计 > 运行审计生成问题报告。
- 在Xcode中打开
- Web测试:
- 打开Chrome DevTools。
- 检查节点下的HTML树。
semantics host - 导航至元素标签,打开无障碍子标签检查导出的ARIA数据。
- 通过以下命令运行应用,可视化语义节点:。
flutter run -d chrome --profile --dart-define=FLUTTER_WEB_DEBUG_SHOW_SEMANTICS=true
Task Progress: Automated Testing
任务进度:自动化测试
Integrate Flutter's Accessibility Guideline API into your widget tests to catch contrast, target size, and labeling issues automatically.
- Create a dedicated test file (e.g., ).
test/a11y_test.dart - Initialize the semantics handle using .
tester.ensureSemantics() - Assert against (48x48px minimum).
androidTapTargetGuideline - Assert against (44x44px minimum).
iOSTapTargetGuideline - Assert against .
labeledTapTargetGuideline - Assert against (3:1 minimum for large text).
textContrastGuideline - Dispose of the semantics handle at the end of the test.
将Flutter的无障碍指南API集成到组件测试中,自动检测对比度、目标尺寸和标签问题。
- 创建专用测试文件(如)。
test/a11y_test.dart - 使用初始化语义句柄。
tester.ensureSemantics() - 断言符合(最小48x48px)。
androidTapTargetGuideline - 断言符合(最小44x44px)。
iOSTapTargetGuideline - 断言符合。
labeledTapTargetGuideline - 断言符合(大文本最小对比度3:1)。
textContrastGuideline - 测试结束时销毁语义句柄。
Debugging the Semantics Tree
语义树调试
When semantic nodes are incorrectly placed or missing, execute the following feedback loop to identify and resolve the discrepancies.
- Run validator: Trigger a dump of the Semantics tree to the console.
- Enable accessibility via a system tool or .
SemanticsDebugger - Invoke (e.g., bind it to a
debugDumpSemanticsTree()'sGestureDetectorcallback for easy triggering during debugging).onTap
- Enable accessibility via a system tool or
- Review errors: Analyze the console output to locate missing labels, incorrect roles, or improperly nested semantic nodes.
- Fix: Wrap the offending widgets in or
Semanticswidgets, apply the correctMergeSemantics, and repeat step 1 until the tree accurately reflects the visual UI.SemanticsRole
当语义节点位置错误或缺失时,执行以下反馈循环来识别并解决问题。
- 运行验证器: 将语义树转储到控制台。
- 通过系统工具或启用无障碍功能。
SemanticsDebugger - 调用(例如,将其绑定到
debugDumpSemanticsTree()的GestureDetector回调,方便调试时触发)。onTap
- 通过系统工具或
- 查看错误: 分析控制台输出,定位缺失的标签、错误的角色或嵌套不当的语义节点。
- 修复: 将有问题的组件包裹在或
Semantics组件中,应用正确的MergeSemantics,重复步骤1直到语义树准确反映视觉UI。SemanticsRole
Examples
示例
Programmatically Enabling Web Accessibility
通过代码启用Web无障碍功能
Force the Semantics tree to build immediately on Flutter Web.
dart
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/semantics.dart';
void main() {
runApp(const MyApp());
if (kIsWeb) {
SemanticsBinding.instance.ensureSemantics();
}
}强制Flutter Web立即构建语义树。
dart
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/semantics.dart';
void main() {
runApp(const MyApp());
if (kIsWeb) {
SemanticsBinding.instance.ensureSemantics();
}
}Explicitly Defining Semantic Roles
显式定义语义角色
Assign explicit list and list-item roles to a custom layout.
dart
import 'package:flutter/material.dart';
import 'package:flutter/semantics.dart';
class MyCustomListWidget extends StatelessWidget {
const MyCustomListWidget({Key? key}) : super(key: key);
Widget build(BuildContext context) {
return Semantics(
role: SemanticsRole.list,
explicitChildNodes: true,
child: Column(
children: <Widget>[
Semantics(
role: SemanticsRole.listItem,
child: const Padding(
padding: EdgeInsets.all(8.0),
child: Text('Content of the first custom list item.'),
),
),
Semantics(
role: SemanticsRole.listItem,
child: const Padding(
padding: EdgeInsets.all(8.0),
child: Text('Content of the second custom list item.'),
),
),
],
),
);
}
}为自定义布局分配明确的列表和列表项角色。
dart
import 'package:flutter/material.dart';
import 'package:flutter/semantics.dart';
class MyCustomListWidget extends StatelessWidget {
const MyCustomListWidget({Key? key}) : super(key: key);
Widget build(BuildContext context) {
return Semantics(
role: SemanticsRole.list,
explicitChildNodes: true,
child: Column(
children: <Widget>[
Semantics(
role: SemanticsRole.listItem,
child: const Padding(
padding: EdgeInsets.all(8.0),
child: Text('Content of the first custom list item.'),
),
),
Semantics(
role: SemanticsRole.listItem,
child: const Padding(
padding: EdgeInsets.all(8.0),
child: Text('Content of the second custom list item.'),
),
),
],
),
);
}
}Automated Accessibility Testing
无障碍自动化测试
Implement the Accessibility Guideline API in a widget test.
dart
import 'package:flutter_test/flutter_test.dart';
import 'package:your_accessible_app/main.dart';
void main() {
testWidgets('Follows a11y guidelines', (tester) async {
final SemanticsHandle handle = tester.ensureSemantics();
await tester.pumpWidget(const AccessibleApp());
// Check tap target sizes
await expectLater(tester, meetsGuideline(androidTapTargetGuideline));
await expectLater(tester, meetsGuideline(iOSTapTargetGuideline));
// Check labels and contrast
await expectLater(tester, meetsGuideline(labeledTapTargetGuideline));
await expectLater(tester, meetsGuideline(textContrastGuideline));
handle.dispose();
});
}在组件测试中实现无障碍指南API。
dart
import 'package:flutter_test/flutter_test.dart';
import 'package:your_accessible_app/main.dart';
void main() {
testWidgets('Follows a11y guidelines', (tester) async {
final SemanticsHandle handle = tester.ensureSemantics();
await tester.pumpWidget(const AccessibleApp());
// 检查点击目标尺寸
await expectLater(tester, meetsGuideline(androidTapTargetGuideline));
await expectLater(tester, meetsGuideline(iOSTapTargetGuideline));
// 检查标签和对比度
await expectLater(tester, meetsGuideline(labeledTapTargetGuideline));
await expectLater(tester, meetsGuideline(textContrastGuideline));
handle.dispose();
});
}