Autocomplete在showGeneralDialog中VoiceOver/Talkback无障碍交互异常
Dialog中Autocomplete组件的无障碍支持修复方案
问题场景
使用Autocomplete组件实现输入补全,功能逻辑正常,但当组件嵌入showGeneralDialog弹出的对话框时,VoiceOver/Talkback无法正常操作下拉菜单:
- 无法通过屏幕阅读器选中下拉项,双击也触发不了
onSelected回调 - 屏幕阅读器无法朗读下拉项的内容
问题原因
showGeneralDialog默认的无障碍容器配置未正确纳入Autocomplete的下拉菜单,导致下拉项无法被屏幕阅读器识别为可交互元素,焦点管理也出现异常。
修复方案
1. 给Dialog设置无障碍容器属性
用Semantics组件包裹Dialog的根内容,标记为独立的无障碍容器,确保下拉菜单能被识别为容器的子元素:
pageBuilder: (context, animation, secondaryAnimation) { return Semantics( container: true, label: '搜索对话框', // 给对话框设置无障碍标签 child: Scaffold( // ... 原有Dialog内容 ), ); }
2. 自定义下拉选项的无障碍语义
重写Autocomplete的optionsViewBuilder,给每个下拉选项添加Semantics组件,明确设置可点击属性和朗读标签:
optionsViewBuilder: (context, onSelected, options) { return Align( alignment: Alignment.topLeft, child: Material( elevation: 4.0, child: SizedBox( height: 200, width: 300, child: ListView.builder( padding: EdgeInsets.zero, itemCount: options.length, itemBuilder: (context, index) { final option = options.elementAt(index); return Semantics( label: option, // 指定屏幕阅读器朗读的文本 button: true, // 标记为可点击按钮 onTap: () => onSelected(option), child: ListTile( title: Text(option), onTap: () => onSelected(option), ), ); }, ), ), ), ); }
3. 确保焦点正确流转
确保Dialog的barrierDismissible设置合理,同时Autocomplete的fieldViewBuilder中正确传递focusNode,避免焦点被Dialog拦截。
完整修复代码示例
showGeneralDialog( context: context, pageBuilder: (context, animation, secondaryAnimation) { return Semantics( container: true, label: '搜索对话框', child: Scaffold( backgroundColor: Colors.white, body: Padding( padding: const EdgeInsets.all(16.0), child: Autocomplete<String>( optionsBuilder: (TextEditingValue textEditingValue) { if (textEditingValue.text.isEmpty) { return const Iterable<String>.empty(); } return ['Apple', 'Banana', 'Orange'] .where((option) => option.toLowerCase().contains(textEditingValue.text.toLowerCase())); }, displayStringForOption: (option) => option, onSelected: (String selection) { debugPrint('选中了:$selection'); }, fieldViewBuilder: (context, textEditingController, focusNode, onFieldSubmitted) { return TextField( controller: textEditingController, focusNode: focusNode, decoration: const InputDecoration( labelText: '搜索', border: OutlineInputBorder(), ), onSubmitted: onFieldSubmitted, ); }, optionsViewBuilder: (context, onSelected, options) { return Align( alignment: Alignment.topLeft, child: Material( elevation: 4.0, child: SizedBox( height: 200, width: 300, child: ListView.builder( padding: EdgeInsets.zero, itemCount: options.length, itemBuilder: (context, index) { final option = options.elementAt(index); return Semantics( label: option, button: true, onTap: () => onSelected(option), child: ListTile( title: Text(option), onTap: () => onSelected(option), ), ); }, ), ), ), ); }, ), ), ), ); }, );
内容的提问来源于stack exchange,提问作者BLKKKBVSIK
相关产品推荐
相关产品推荐

