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 组件的使用。那么本就到这里,谢谢观看 ~

相关推荐
aningxiaoxixi几秒前
Android 之 audiotrack
android
枷锁—sha3 分钟前
【DVWA系列】——CSRF——Medium详细教程
android·服务器·前端·web安全·网络安全·csrf
Cao_Shixin攻城狮4 小时前
Flutter运行Android项目时显示java版本不兼容(Unsupported class file major version 65)的处理
android·java·flutter
呼啦啦呼啦啦啦啦啦啦7 小时前
利用pdfjs实现的pdf预览简单demo(包含翻页功能)
android·javascript·pdf
idjl8 小时前
Mysql测试题
android·adb
游戏开发爱好者811 小时前
iOS App 电池消耗管理与优化 提升用户体验的完整指南
android·ios·小程序·https·uni-app·iphone·webview
人生游戏牛马NPC1号11 小时前
学习 Flutter (四):玩安卓项目实战 - 中
android·学习·flutter
星辰也为你祝福h13 小时前
Android原生Dialog
android
梁同学与Android13 小时前
Android ---【CPU优化】需要优化的原因及优化的地方
android
Misha韩14 小时前
React Native 基础tabBar和自定义tabBar - bottom-tabs
android·react native