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

2. 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();
}
}
3. DropdownMenu 样式配置
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 的代码实现。
4. DropdownMenu 源码实现简看
DropdownMenu 是一个 StatefulWidget
,通过状态类 _DropdownMenuState
维护状态数据以及处理视图的构建逻辑。

从构建逻辑上来看最主要依赖 Shortcuts
、MenuAnchor
、TextFiled
等组件:

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

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

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

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