You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

解决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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.01 05:57:03