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

Flutter:如何向下拉菜单列表添加搜索TextField与TextButton两项?

在Flutter下拉菜单中添加搜索框和按钮的实现方案

Flutter默认的DropdownButton无法直接嵌入输入框和按钮这类交互组件,我们可以通过两种方式实现高度定制的下拉菜单,满足需求:

方式一:使用PopupMenuButton自定义弹出内容

通过PopupMenuButton的itemBuilder可以灵活组合搜索框、按钮和选项列表,代码示例如下:

import 'package:flutter/material.dart';

class CustomDropdown extends StatefulWidget {
  const CustomDropdown({super.key});

  @override
  State<CustomDropdown> createState() => _CustomDropdownState();
}

class _CustomDropdownState extends State<CustomDropdown> {
  // 模拟下拉选项数据
  final List<String> _options = ['选项1', '选项2', '选项3', '选项4', '选项5'];
  String? _selectedOption;
  final TextEditingController _searchController = TextEditingController();
  List<String> _filteredOptions = [];

  @override
  void initState() {
    super.initState();
    _filteredOptions = _options;
    _searchController.addListener(_filterOptions);
  }

  void _filterOptions() {
    final query = _searchController.text.toLowerCase();
    setState(() {
      _filteredOptions = _options
          .where((option) => option.toLowerCase().contains(query))
          .toList();
    });
  }

  @override
  void dispose() {
    _searchController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return PopupMenuButton<String>(
      // 自定义下拉触发按钮
      child: Container(
        padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
        decoration: BoxDecoration(
          border: Border.all(color: Colors.grey),
          borderRadius: BorderRadius.circular(8),
        ),
        child: Row(
          mainAxisSize: MainAxisSize.min,
          children: [
            Text(_selectedOption ?? '请选择'),
            const SizedBox(width: 8),
            const Icon(Icons.arrow_drop_down),
          ],
        ),
      ),
      // 构建弹出菜单内容
      itemBuilder: (context) {
        return [
          // 搜索输入框
          PopupMenuItem(
            enabled: false, // 禁用点击事件,避免干扰输入
            child: TextField(
              controller: _searchController,
              decoration: const InputDecoration(
                hintText: '搜索选项',
                prefixIcon: Icon(Icons.search),
                border: OutlineInputBorder(),
              ),
            ),
          ),
          // 自定义按钮
          PopupMenuItem(
            enabled: false,
            child: TextButton(
              onPressed: () {
                Navigator.pop(context);
                ScaffoldMessenger.of(context).showSnackBar(
                  const SnackBar(content: Text('自定义按钮被点击')),
                );
              },
              child: const Text('新增选项'),
            ),
          ),
          // 分割线
          const PopupMenuDivider(),
          // 过滤后的选项列表
          ..._filteredOptions.map((option) {
            return PopupMenuItem<String>(
              value: option,
              child: Text(option),
              onTap: () {
                setState(() {
                  _selectedOption = option;
                });
              },
            );
          }),
        ];
      },
    );
  }
}

// 调用示例
void main() {
  runApp(const MaterialApp(
    home: Scaffold(
      body: Center(child: CustomDropdown()),
    ),
  ));
}

关键说明:

  • 用PopupMenuItem包裹搜索框和按钮时,设置enabled: false避免触发菜单的默认点击关闭逻辑
  • 为搜索框添加文本监听,实时过滤选项列表
  • 可完全自定义触发按钮的样式,替代默认外观

方式二:使用showMenu完全自定义弹出布局

如果需要更灵活的布局控制(比如调整组件间距、滚动行为),可以直接调用showMenu方法传入自定义布局:

import 'package:flutter/material.dart';

class CustomDropdown2 extends StatefulWidget {
  const CustomDropdown2({super.key});

  @override
  State<CustomDropdown2> createState() => _CustomDropdown2State();
}

class _CustomDropdown2State extends State<CustomDropdown2> {
  final List<String> _options = ['选项1', '选项2', '选项3', '选项4', '选项5'];
  String? _selectedOption;
  final TextEditingController _searchController = TextEditingController();
  List<String> _filteredOptions = [];

  @override
  void initState() {
    super.initState();
    _filteredOptions = _options;
    _searchController.addListener(_filterOptions);
  }

  void _filterOptions() {
    final query = _searchController.text.toLowerCase();
    setState(() {
      _filteredOptions = _options
          .where((option) => option.toLowerCase().contains(query))
          .toList();
    });
  }

  @override
  void dispose() {
    _searchController.dispose();
    super.dispose();
  }

  void _showCustomMenu() async {
    final RenderBox button = context.findRenderObject() as RenderBox;
    final RenderBox overlay = Overlay.of(context).context.findRenderObject() as RenderBox;
    final Offset position = button.localToGlobal(
      Offset.zero,
      ancestor: overlay,
    );

    await showMenu(
      context: context,
      position: RelativeRect.fromLTRB(
        position.dx,
        position.dy + button.size.height,
        position.dx + button.size.width,
        position.dy + button.size.height + 400, // 限制菜单最大高度
      ),
      items: [], // 留空,使用自定义child
      child: Container(
        width: button.size.width,
        constraints: const BoxConstraints(maxHeight: 400),
        child: Column(
          mainAxisSize: MainAxisSize.min,
          children: [
            // 搜索框
            Padding(
              padding: const EdgeInsets.all(8.0),
              child: TextField(
                controller: _searchController,
                decoration: const InputDecoration(
                  hintText: '搜索选项',
                  prefixIcon: Icon(Icons.search),
                  border: OutlineInputBorder(),
                ),
              ),
            ),
            // 自定义按钮
            TextButton(
              onPressed: () {
                Navigator.pop(context);
                ScaffoldMessenger.of(context).showSnackBar(
                  const SnackBar(content: Text('新增选项被点击')),
                );
              },
              child: const Text('新增选项'),
            ),
            const Divider(height: 1),
            // 可滚动的选项列表
            Expanded(
              child: ListView.builder(
                shrinkWrap: true,
                itemCount: _filteredOptions.length,
                itemBuilder: (context, index) {
                  final option = _filteredOptions[index];
                  return ListTile(
                    title: Text(option),
                    onTap: () {
                      setState(() {
                        _selectedOption = option;
                      });
                      Navigator.pop(context);
                    },
                  );
                },
              ),
            ),
          ],
        ),
      ),
    );
  }

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onTap: _showCustomMenu,
      child: Container(
        padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
        decoration: BoxDecoration(
          border: Border.all(color: Colors.grey),
          borderRadius: BorderRadius.circular(8),
        ),
        child: Row(
          mainAxisSize: MainAxisSize.min,
          children: [
            Text(_selectedOption ?? '请选择'),
            const SizedBox(width: 8),
            const Icon(Icons.arrow_drop_down),
          ],
        ),
      ),
    );
  }
}

// 调用示例
void main() {
  runApp(const MaterialApp(
    home: Scaffold(
      body: Center(child: CustomDropdown2()),
    ),
  ));
}

关键说明:

  • 通过showMenu的child参数传入完全自定义的布局,自由组合各类组件
  • 手动计算菜单弹出位置,确保显示在触发按钮下方
  • 使用ListView.builder处理大量选项的滚动需求,提升性能

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.29 05:12:09