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

相关推荐
alexhilton1 天前
Jetpack Compose内部的不同节点类型
android·kotlin·android jetpack
Frank_HarmonyOS1 天前
Android中四大组件之一的Activity的启动模式
android
似霰1 天前
HIDL Hal 开发笔记7----简单 HIDL HAL 实现
android·framework·hal
用户2018792831671 天前
📚 Android Settings系统:图书馆管理员的故事
android
青莲8431 天前
Android 事件分发机制 - 事件流向详解
android·前端·面试
火柴就是我1 天前
学习一些常用的混合模式之BlendMode. dst_atop
android·flutter
火柴就是我1 天前
学习一些常用的混合模式之BlendMode. dstIn
android·flutter
ganshenml2 天前
【Android】 开发四角版本全解析:AS、AGP、Gradle 与 JDK 的配套关系
android·java·开发语言
我命由我123452 天前
Kotlin 运算符 - == 运算符与 === 运算符
android·java·开发语言·java-ee·kotlin·android studio·android-studio
摘星编程2 天前
【RAG+LLM实战指南】如何用检索增强生成破解AI幻觉难题?
android·人工智能