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

如何通过键盘控制Flutter的Autocomplete Widget

实现RawAutocomplete的键盘导航功能

要实现Web端的键盘控制(上下箭头切换选项、回车键确认),需添加状态管理和键盘事件监听,以下是修改后的完整代码:

class _MyHomePageState extends State<MyHomePage> {
  final controller = TextEditingController();
  final focus = FocusNode();
  bool loadingAutocomplete = false;
  // 记录当前选中的选项索引
  int _selectedOptionIndex = -1;
  // 存储当前的选项列表,用于键盘导航时获取对应选项
  List<String> _currentOptions = [];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
        title: Text(widget.title),
      ),
      body: RawAutocomplete(
        textEditingController: controller,
        focusNode: focus,
        optionsBuilder: (TextEditingValue textEditingValue) async {
          _currentOptions = await getCities(textEditingValue.text);
          // 每次获取新选项后重置选中索引
          setState(() => _selectedOptionIndex = -1);
          return _currentOptions;
        },
        fieldViewBuilder: (BuildContext context,
            TextEditingController controller,
            FocusNode focusNode,
            VoidCallback onFieldSubmitted) {
          return TextField(
            controller: controller,
            focusNode: focusNode,
            onSubmitted: (value) {
              // 回车时如果有选中的选项,触发选中逻辑
              if (_selectedOptionIndex >= 0 && _selectedOptionIndex < _currentOptions.length) {
                RawAutocomplete.onSelected(_currentOptions[_selectedOptionIndex])(context);
              }
            },
            // 处理键盘事件:上下箭头切换选中项
            onKeyEvent: (node, event) {
              if (_currentOptions.isEmpty) return KeyEventResult.ignored;

              if (event.logicalKey == LogicalKeyboardKey.arrowDown) {
                setState(() {
                  _selectedOptionIndex = (_selectedOptionIndex + 1) % _currentOptions.length;
                });
                return KeyEventResult.handled;
              } else if (event.logicalKey == LogicalKeyboardKey.arrowUp) {
                setState(() {
                  _selectedOptionIndex = (_selectedOptionIndex - 1) < 0 
                      ? _currentOptions.length - 1 
                      : _selectedOptionIndex - 1;
                });
                return KeyEventResult.handled;
              } else if (event.logicalKey == LogicalKeyboardKey.enter) {
                if (_selectedOptionIndex >= 0 && _selectedOptionIndex < _currentOptions.length) {
                  RawAutocomplete.onSelected(_currentOptions[_selectedOptionIndex])(context);
                  // 选中后重置索引
                  setState(() => _selectedOptionIndex = -1);
                }
                return KeyEventResult.handled;
              }
              return KeyEventResult.ignored;
            },
            decoration: const InputDecoration(
              labelText: 'Cities',
            ),
          );
        },
        optionsViewBuilder: (context, onSelected, options) => Padding(
          padding: const EdgeInsets.only(top: 4),
          child: Align(
            alignment: Alignment.topLeft,
            child: Material(
              color: Colors.white,
              shape: const RoundedRectangleBorder(
                borderRadius: BorderRadius.vertical(bottom: Radius.circular(4)),
              ),
              child: SizedBox(
                width: 300,
                child: ListView.builder(
                  padding: EdgeInsets.zero,
                  itemCount: options.length,
                  shrinkWrap: false,
                  itemBuilder: (BuildContext context, int index) {
                    final String option = options.elementAt(index);
                    // 给当前选中的选项添加高亮背景
                    final isSelected = index == _selectedOptionIndex;

                    return InkWell(
                      onTap: () {
                        onSelected(option);
                        setState(() => _selectedOptionIndex = -1);
                      },
                      child: Container(
                        color: isSelected ? Colors.grey[200] : Colors.transparent,
                        padding: const EdgeInsets.all(16.0),
                        child: Text(option),
                      ),
                    );
                  },
                ),
              ),
            ),
          ),
        ),
      ),
    );
  }

  Future<List<String>> getCities(String value) async {
    await Future.delayed(const Duration(seconds: 1));

    List<String> cities = [
      'New York',
      'Los Angeles',
      'Chicago',
      'Houston',
      'Phoenix',
      'Philadelphia',
      'San Antonio',
      'San Diego',
      'Dallas',
      'San Jose'
    ];

    return cities
        .where((element) => element.toLowerCase().contains(value.toLowerCase()))
        .toList();
  }
}

关键修改说明:

  • 新增状态变量:_selectedOptionIndex 记录当前选中的选项索引,_currentOptions 存储当前加载的选项列表,用于键盘导航时获取对应选项。
  • 键盘事件处理:通过onKeyEvent监听上下箭头和回车键:
    • 下箭头:索引自增,超出列表长度时回到第一个选项
    • 上箭头:索引自减,小于0时回到最后一个选项
    • 回车键:触发选中当前索引对应的选项,并重置选中状态
  • 选中项高亮:在optionsViewBuilder中,给当前选中的选项添加灰色背景,让用户直观看到选中状态
  • 选项更新时重置索引:每次获取新的选项列表后,重置_selectedOptionIndex为-1,避免旧索引干扰新列表

内容的提问来源于stack exchange,提问作者David Escariz

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.11 19:40:21