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

Flutter 如何实现附图所示类型的下拉菜单(Dropdown)组件

Flutter 自定义样式下拉菜单实现方案

你需要实现的这类自定义外观下拉菜单参考效果如下:
自定义下拉菜单效果图
可以通过以下两种常用方案实现,可根据你的样式复杂度选择:

方案1:基于原生DropdownButton扩展(适配简单自定义需求)

Flutter 自带的DropdownButton提供了丰富的样式配置参数,简单调整即可匹配大部分设计需求:

  • 核心参数配置:
    • 用value绑定当前选中值,onChanged监听选项切换事件
    • 用items配置下拉选项列表,可自定义每个选项的内边距、文字样式
    • 设置isExpanded: true让下拉组件撑满父容器宽度
  • 常见样式调整:
    • 用dropdownColor设置下拉面板背景色
    • 用icon参数替换默认的下拉箭头图标
    • 用underline参数隐藏默认下划线,或者自定义下划线样式
    • 用elevation调整下拉面板的阴影高度

示例代码参考:

String? selectedValue = '选项1';

@override
Widget build(BuildContext context) {
  return Container(
    padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
    decoration: BoxDecoration(
      border: Border.all(color: Colors.grey.shade300),
      borderRadius: BorderRadius.circular(8),
    ),
    child: DropdownButton<String>(
      value: selectedValue,
      icon: const Icon(Icons.keyboard_arrow_down, color: Colors.grey, size: 20),
      elevation: 8,
      style: const TextStyle(color: Colors.black, fontSize: 14),
      dropdownColor: Colors.white,
      underline: const SizedBox.shrink(), // 隐藏默认下划线
      isExpanded: true,
      isDense: true,
      onChanged: (String? newValue) {
        setState(() {
          selectedValue = newValue;
        });
      },
      items: <String>['选项1', '选项2', '选项3', '选项4']
          .map<DropdownMenuItem<String>>((String value) {
        return DropdownMenuItem<String>(
          value: value,
          child: Text(value),
        );
      }).toList(),
    ),
  );
}

方案2:完全自定义下拉面板(适配复杂自定义需求)

如果需要实现选项分割线、选中态高亮、下拉搜索等复杂功能,原生DropdownButton的扩展性不足以满足需求,可以结合showMenu方法自定义弹出层实现:

  • 用GestureDetector包裹触发下拉的组件,点击时计算弹出位置
  • 调用showMenu弹出自定义菜单,可自由组合搜索框、分割线、自定义选项组件
  • 选中选项后回传值更新触发区域的显示内容

示例代码参考:

String? selectedValue = '选项1';

// 触发自定义下拉
void _showCustomDropdown(BuildContext context) async {
  final renderBox = context.findRenderObject() as RenderBox;
  final offset = renderBox.localToGlobal(Offset.zero);

  // 弹出自定义菜单
  final selected = await showMenu<String>(
    context: context,
    // 计算菜单弹出位置,紧跟在触发组件下方
    position: RelativeRect.fromLTRB(
      offset.dx,
      offset.dy + renderBox.size.height + 4,
      offset.dx + renderBox.size.width,
      offset.dy,
    ),
    shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
    elevation: 8,
    items: [
      // 可自定义添加搜索框
      const PopupMenuItem(
        enabled: false,
        child: TextField(
          decoration: InputDecoration(
            hintText: '搜索选项',
            prefixIcon: Icon(Icons.search, size: 18),
            border: InputBorder.none,
            isDense: true,
            contentPadding: EdgeInsets.zero,
          ),
        ),
      ),
      const PopupMenuDivider(),
      PopupMenuItem(value: '1', child: const Text('选项1')),
      PopupMenuItem(value: '2', child: const Text('选项2')),
      const PopupMenuDivider(),
      PopupMenuItem(value: '3', child: const Text('选项3')),
    ],
  );

  if (selected != null) {
    setState(() {
      selectedValue = selected;
    });
  }
}

@override
Widget build(BuildContext context) {
  return GestureDetector(
    onTap: () => _showCustomDropdown(context),
    child: Container(
      padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10),
      decoration: BoxDecoration(
        border: Border.all(color: Colors.grey.shade300),
        borderRadius: BorderRadius.circular(8),
      ),
      child: Row(
        mainAxisAlignment: MainAxisAlignment.spaceBetween,
        children: [
          Text(selectedValue ?? '请选择', style: const TextStyle(fontSize: 14)),
          const Icon(Icons.keyboard_arrow_down, color: Colors.grey, size: 20),
        ],
      ),
    ),
  );
}

注意事项

  • 自定义弹出层时要适配滚动场景,可计算滚动偏移调整弹出位置,避免菜单错位
  • 需要支持多选的话,可以把弹出层改成StatefulWidget,记录选中状态后一次性回传结果
  • 下拉面板的宽度默认和触发组件宽度一致,需要调整的话可以自定义PopupRoute实现自定义尺寸

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.06 02:06:03