Flutter 组件集录 | 下拉菜单 DropdownMenu 组件

1. 前言

Flutter 框架中新增了 DropdownMenu 下拉按钮,可以让我们更方便地实现下拉选择的交互。本文案例源码可以详见 【FlutterUnit 的 DropdownMenu】


首先通过一个最简单的案例体验一下 DropdownMenu 的使用,如下所示:

  • 点击使会下拉展示菜单选项,选择科目 ;
  • 点击时选中科目,下方的文本相应变化;
  • 支持输入定位到指定的菜单条目;

实现的代码如下,DropdownMenu 组件支持一个泛型,案例中使用了下面几个配置参数:

参数名 类型 介绍
dropdownMenuEntries List<DropdownMenuEntry<T>> 必须传入,菜单条目列表
initialSelection T? 初始选项值
onSelected ValueChanged<T?>? 选中条目回调事件
menuHeight double 菜单高度
width double 输入框宽度
dart 复制代码
class DropdownMenuNode1 extends StatefulWidget {
  const DropdownMenuNode1({super.key});

  @override
  State<DropdownMenuNode1> createState() => _DropdownMenuNode1State();
}

class _DropdownMenuNode1State extends State<DropdownMenuNode1> {
  final List<String> data = ['语文', '数学', '英语', '物理', '化学', '生物', '地理'];
  late String _dropdownValue = data.first;

  @override
  Widget build(BuildContext context) {
    return Column(
      mainAxisSize: MainAxisSize.min,
      children: [
        DropdownMenu<String>(
          menuHeight: 200,
          initialSelection: data.first,
          onSelected: _onSelect,
          dropdownMenuEntries: _buildMenuList(data),
        ),
        const SizedBox(height: 8,),
        Text('你选择的学科是: $_dropdownValue')
      ],
    );
  }

  void _onSelect(String? value) {
    setState(() {
      _dropdownValue = value!;
    });
  }

  List<DropdownMenuEntry<String>> _buildMenuList(List<String> data) {
    return data.map((String value) {
      return DropdownMenuEntry<String>(value: value, label: value);
    }).toList();
  }
}

DropdownMenu 本质上是由 TextField + MenuAnchor 实现的,所以样式配置上面主要和这两个组件有关。

参数名 类型 介绍
controller TextEditingController? 文字输入控制器
label Widget? 输入框标签
textStyle TextStyle? 输入框文字样式
inputDecorationTheme InputDecorationTheme? 输入框装饰主题
leadingIcon Widget? 左侧图标
trailingIcon Widget? 右侧为展开菜单时图标
selectedTrailingIcon Widget? 右侧展开菜单时图标
hintText String? 输入框提示文字
helperText String? 输入框辅助文字
errorText String? 输入框错误文字
menuStyle MenuStyle? 弹出菜单样式

下面是右侧选择图标的 DropdownMenu 组件构建逻辑,其中

  • requestFocusOnTap: 点击时是否获取焦点,置为 true 在移动端上会弹出软键盘,桌面端无法输入。
  • enableFilter: 弹出菜单项是否以当前内容搜索,如果为 true, 会因为过滤使得菜单响应减少。
dart 复制代码
Widget _buildLabelMenu() {
  return DropdownMenu<IconLabel>(
    controller: iconController,
    enableFilter: false,
    requestFocusOnTap: true,
    leadingIcon: const Icon(Icons.search),
    label: const Text('Icon'),
    inputDecorationTheme: const InputDecorationTheme(
      filled: true,
      contentPadding: EdgeInsets.symmetric(vertical: 5.0),
    ),
    onSelected: (IconLabel? icon) {
      setState(() {
        selectedIcon = icon;
      });
    },
    dropdownMenuEntries: IconLabel.values.map((IconLabel icon) {
        return DropdownMenuEntry<IconLabel>(
          value: icon,
          label: icon.label,
          leadingIcon: Icon(icon.icon),
        );
      },
    ).toList(),
  );
}

下面是左侧选择颜色的 DropdownMenu 组件构建逻辑,其中

  • menuStyle 可以调节菜单面板的样式,比如背景色、边距、最大最小尺寸、形状等。
  • dropdownMenuEntries 中可以通过 DropdownMenuEntry 的 enable 参数设置是否禁用菜单项。
dart 复制代码
Widget _buildColorMenu(){
  return DropdownMenu<ColorLabel>(
    initialSelection: ColorLabel.green,
    controller: colorController,
    requestFocusOnTap: true,
    label: const Text('Color'),
    menuHeight: 150,
    menuStyle: const MenuStyle(
      backgroundColor: MaterialStatePropertyAll<Color>(Colors.white),
      surfaceTintColor: MaterialStatePropertyAll<Color>(Colors.white),
      padding: MaterialStatePropertyAll<EdgeInsetsGeometry>(EdgeInsets.symmetric(vertical: 20)),
    ),
    onSelected: (ColorLabel? color) {
      setState(() {
        selectedColor = color;
      });
    },
    dropdownMenuEntries: ColorLabel.values.map((ColorLabel color) {
          return DropdownMenuEntry<ColorLabel>(
            value: color,
            label: color.label,
            enabled: color.label != 'Grey',
            style: MenuItemButton.styleFrom(
              foregroundColor: color.color,
            ),
          );
        }
    ).toList(),
  );
}

另外,如果 DropdownMenu 的菜单条目比较复杂,想要定制展示内容,可以通过 DropdownMenuEntry 的 labelWidget 构建,如下所示,根据 User 对象构建菜单条目。

dart 复制代码
class _UserItem extends StatelessWidget {
  final User user;

  const _UserItem({
    Key? key,
    required this.user,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Container(
      padding: const EdgeInsets.symmetric(horizontal: 0, vertical: 6),
      child: Row(
        crossAxisAlignment: CrossAxisAlignment.center,
        children: [
          CircleAvatar(
            foregroundColor: Colors.transparent,
            backgroundImage:
            AssetImage('assets/images/head_icon/${user.image}'),
          ),
          const SizedBox(width: 20),
          Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            mainAxisSize: MainAxisSize.min,
            children: [
              Text(user.name),
              Text(
                '性别: ${user.man ? '男' : '女'}',
                style: const TextStyle(color: Colors.grey),
              ),
            ],
          ),
        ],
      ),
    );
  }
}

到这里,DropdownMenu 组件使用相关的属性就介绍的差不多了。下面来简单瞄一眼 DropdownMenu 的代码实现。


DropdownMenu 是一个 StatefulWidget ,通过状态类 _DropdownMenuState 维护状态数据以及处理视图的构建逻辑。

从构建逻辑上来看最主要依赖 ShortcutsMenuAnchorTextFiled 等组件:

其中 Shortcuts 在最顶层,和 Actions 联合使用处理键盘快捷键事件。比如菜单栏展开时 按键可以上下激活选中菜单。借此我们也可以学到如何让一个组件响应快捷键处理逻辑。


其中最核心的视图表现是对 MenuAnchor 组件的封装,在 builder 回调中构建输入框、首尾按钮等展示内容。内容的排列通过 _DropdownMenuBody完成;菜单列表是 menuChildern 属性,传入 menu :

其中 menu 对象是通过 _buildButtons 构造的组件列表,也就是 DropdownMenuEntry 列表形成的菜单项:

DropdownMenu 的核心逻辑也就这些,它是对 MenuAnchor 使用的一个简单封装,如果希望定制化更过细节,也可以自己通过 MenuAnchor 来实现。之后有机会,会详细介绍一下 MenuAnchor 组件的使用。那么本就到这里,谢谢观看 ~

相关推荐
Java猿_7 小时前
Spring Boot 集成 Sa-Token 实现登录认证与 RBAC 权限控制(实战)
android·spring boot·后端
子春一28 小时前
Flutter 2025 性能工程体系:从启动优化到帧率稳定,打造丝滑、省电、低内存的极致用户体验
flutter·ux
子春一28 小时前
Flutter 2025 性能工程体系:从启动优化到帧率稳定,打造丝滑如原生的用户体验
flutter·ux
子春一29 小时前
Flutter 2025 架构演进工程体系:从单体到模块化,构建可扩展、可协作、可持续的大型应用
flutter·架构
Jerry10 小时前
Jetpack Compose Navigation
android
renke336410 小时前
Flutter 2025 国际化与本地化工程体系:打造真正全球化的应用体验
flutter
子春一210 小时前
Flutter 2025 可访问性(Accessibility)工程体系:从合规达标到包容设计,打造人人可用的数字产品
前端·javascript·flutter
介一安全10 小时前
【Frida Android】实战篇17:Frida检测与绕过——基于inline hook的攻防实战
android·网络安全·逆向·安全性测试·frida
renke336410 小时前
Flutter 2025 状态管理工程体系:从 setState 到响应式架构,构建可维护、高性能的状态流
flutter
麦客奥德彪11 小时前
Flutter 性能优化完整指南
flutter