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

相关推荐
EngZegNgi7 分钟前
安卓应用启动崩溃的问题排查记录
android·crash·启动崩溃
火柴就是我1 小时前
每日见闻之Container Decoration
android·flutter
天枢破军1 小时前
【AOSP】解决repo拉取提示无法连接android.googlesource.com
android
whysqwhw1 小时前
OkHttp之AndroidPlatform类分析
android
XiaolongTu1 小时前
Kotlin Flow详述:从一个“卡顿”问题到线程切换的本质
android·面试
Kapaseker1 小时前
全网最详细的Compose Stable讲解,你一定要看
android
solo_991 小时前
使用Android Studio 聊微信
android
whysqwhw1 小时前
OkHttp PublicSuffix包的平台化设计分析
android
whysqwhw1 小时前
Conscrypt 源码分析全图解(附精要讲解)
android
一只柠檬新2 小时前
Kotlin object单例到底是懒汉式还是饿汉式
android·kotlin