解决Flutter DropdownButton长按回车触发路由错误问题
问题描述
我正尝试在Flutter分页表格中复刻Excel的导航功能,即长按回车键可向下移动多行。表格的每个单元格包含按钮,点击后会替换为TextFormField或DropdownButton,选择完成后会切换焦点到下一列。相关代码如下:
@override Widget build(BuildContext context) { RegisterProvider registerProvider = Provider.of<RegisterProvider>(context); LineItem item = widget.item; int index = item.index!; // Show the dropdown if this index is selected and return the dropdown button: if (registerProvider.selectedIndex == index && registerProvider.selectedColumn == RegisterProvider.typeColumn) { // Get the focus node for the corresponding cell: FocusNode node = registerProvider.focusNodes[RegisterProvider.typeColumn]![index]; // Returns the dropdown to selected the type: return DropdownButton<String>( focusNode: node, value: item.type, items: LineItem.types.map<DropdownMenuItem<String>>((String value) { return DropdownMenuItem<String>( value: value, child: Text(value), ); }).toList(), onChanged: (newType) { // print("Changed type"); // bool res = registerProvider.changeIsDisabled(); if (newType != null) { // Update the selected type: item.type = newType; // The list of possible charges for the selected type: List<String> chargeSelection = LineItem.correspondingcharges[newType]!; // Set the charge as the first charge: item.charge = chargeSelection.first; // Set the pressure test: item.setWorkingPressure(); // Determine if the new charge selection has only one value there is no need to move to the charge column: if (chargeSelection.length == 1) { // Update the selected column: registerProvider.selectedColumn = RegisterProvider.signageColumn; // This puts the cursor in the next dropdown button: registerProvider.focusNodes[registerProvider.selectedColumn] ?[index] .requestFocus(); registerProvider.refresh(); } else { // Update the selected column: registerProvider.selectedColumn = RegisterProvider.chargeColumn; registerProvider.refresh(); // This puts the cursor in the next dropdownbutton input: registerProvider.focusNodes[RegisterProvider.chargeColumn]![index] .requestFocus(); } } }, ); } return InkWell( child: Padding( padding: const EdgeInsets.all(8.0), // Space to make the selection bigger: child: Text( item.type ?? 'Choose Type', style: TextStyle(color: item.type == null ? Colors.red : null), ), ), onTap: () { // Update the selected index: registerProvider.selectedIndex = index; // Update the selected column: registerProvider.selectedColumn = RegisterProvider.typeColumn; // Refresh the page: registerProvider.refresh(); // This puts the cursor in the text field input: registerProvider.focusNodes[RegisterProvider.typeColumn]?[index] .requestFocus(); }, ); }
长按回车键时出现如下错误:
══╡ EXCEPTION CAUGHT BY SERVICES LIBRARY ╞══════════════════════════════════════════════════════════ The following assertion was thrown while processing the key message handler: Assertion failed: file:///C:/src/flutter/packages/flutter/lib/src/material/dropdown.dart:1339:12 _dropdownRoute == null is not true When the exception was thrown, this was the stack: dart-sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/errors.dart 294:3 throw_ dart-sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/errors.dart 35:3 assertFailed packages/flutter/src/material/dropdown.dart 1339:27 [_handleTap] packages/flutter/src/material/ink_well.dart 845:21 activateOnIntent packages/flutter/src/widgets/actions.dart 597:39 invoke packages/flutter/src/widgets/actions.dart 338:16 [_invoke] packages/flutter/src/widgets/actions.dart 666:27 invokeActionIfEnabled packages/flutter/src/widgets/shortcuts.dart 851:81 handleKeypress packages/flutter/src/widgets/shortcuts.dart 1042:20 [_handleOnKey] ...
需要解决:如何防止DropdownButton在长按回车时被选中,避免该路由错误?
解决方案
这个错误是因为长按回车时,系统会触发DropdownButton的菜单激活逻辑,导致路由状态冲突。可以通过以下两种方式解决:
方法1:用Shortcuts组件屏蔽回车长按事件
在DropdownButton外层包裹Shortcuts组件,覆盖默认的重复回车事件处理,让长按回车只执行你的导航逻辑,不触发下拉菜单:
// 替换原DropdownButton的返回代码 return Shortcuts( shortcuts: { // 拦截重复触发的回车事件(长按回车会产生重复事件) const SingleActivator(LogicalKeyboardKey.enter, isRepeat: true): DoNothingAction(), }, child: DropdownButton<String>( focusNode: node, value: item.type, items: LineItem.types.map<DropdownMenuItem<String>>((String value) { return DropdownMenuItem<String>( value: value, child: Text(value), ); }).toList(), onChanged: (newType) { if (newType != null) { item.type = newType; List<String> chargeSelection = LineItem.correspondingcharges[newType]!; item.charge = chargeSelection.first; item.setWorkingPressure(); if (chargeSelection.length == 1) { registerProvider.selectedColumn = RegisterProvider.signageColumn; registerProvider.focusNodes[registerProvider.selectedColumn]?[index].requestFocus(); registerProvider.refresh(); } else { registerProvider.selectedColumn = RegisterProvider.chargeColumn; registerProvider.refresh(); registerProvider.focusNodes[RegisterProvider.chargeColumn]![index].requestFocus(); } } }, ), );
方法2:通过FocusNode拦截按键事件
给DropdownButton的FocusNode添加onKey回调,识别长按回车事件并阻止它传递给DropdownButton:
// 修改获取focusNode的代码,添加onKey回调 FocusNode node = registerProvider.focusNodes[RegisterProvider.typeColumn]![index] ..onKey = (node, event) { // 识别重复的回车事件(长按回车触发) if (event.logicalKey == LogicalKeyboardKey.enter && event.isRepeat) { // 这里执行你的长按回车导航逻辑,比如向下移动多行 // registerProvider.handleLongPressEnter(index); return KeyEventResult.handled; // 阻止事件继续传递 } return KeyEventResult.ignored; // 其他事件正常处理 }; // 原DropdownButton代码保持不变 return DropdownButton<String>( focusNode: node, value: item.type, items: LineItem.types.map<DropdownMenuItem<String>>((String value) { return DropdownMenuItem<String>( value: value, child: Text(value), ); }).toList(), onChanged: (newType) { // 原onChanged逻辑不变 if (newType != null) { item.type = newType; List<String> chargeSelection = LineItem.correspondingcharges[newType]!; item.charge = chargeSelection.first; item.setWorkingPressure(); if (chargeSelection.length == 1) { registerProvider.selectedColumn = RegisterProvider.signageColumn; registerProvider.focusNodes[registerProvider.selectedColumn]?[index].requestFocus(); registerProvider.refresh(); } else { registerProvider.selectedColumn = RegisterProvider.chargeColumn; registerProvider.refresh(); registerProvider.focusNodes[RegisterProvider.chargeColumn]![index].requestFocus(); } } }, );
注意事项
- 方法1适合全局统一处理多个DropdownButton的场景;方法2针对单个DropdownButton精准控制。
- 确保在拦截事件后,你的长按回车导航逻辑能正常执行,不破坏原有的Excel式导航体验。
内容的提问来源于stack exchange,提问作者Brandon Pillay
相关产品推荐
相关产品推荐

