问题背景

客户端日常开发和学习过程,下拉菜单是一个很常见的组件,本文主要介绍flutter中实现下拉菜单组件的一个方案,基于PopupMenuButton来进行实现。

问题分析

PopupMenuButton PopupMenuButton 是一个非常常见的弹出菜单栏。 属性介绍: image.png

问题解决

话不多说,直接上代码 (1)新建MenuItem.dart通用菜单项类,代码如下:

/// author baorant
/// 通用菜单项
class MenuItem {
  // 显示的文本
  String label;
  // 选中的值
  dynamic value;
  // 是否选中
  bool checked;

  MenuItem({this.label = '', this.value, this.checked = false});
}

(2)实现SelectWidget.dart下拉菜单项组件,代码如下:

import 'package:flutter/material.dart';
import 'MenuItem.dart';

/// @author baorant
/// @创建时间:2024/4/11
/// 下拉菜单按钮组件
class SelectWidget extends StatefulWidget {
  // 显示的菜单项
  final List<MenuItem> items;
  // 当前选中的值
  final dynamic value;
  // 选择框前的标题
  final String? title;
  // 提示语
  final String tooltip;
  // 选中数据的回调事件
  final ValueChanged<dynamic>? valueChanged;
  const SelectWidget(
      {Key? key,
        this.items = const [],
        this.value,
        this.valueChanged,
        this.title,
        this.tooltip = "点击选择"})
      : super(key: key);

  @override
  State<SelectWidget> createState() => _SelectWidgetState();
}

class _SelectWidgetState extends State<SelectWidget> {
  String label = '请选择';
  // 是否展开下拉按钮
  bool isExpand = false;
  // 当前的值
  dynamic currentValue;

  @override
  void initState() {
    currentValue = widget.value;
    super.initState();
  }

  /// 根据当前的value处理当前文本显示
  void initTitle() {
    if (currentValue != null) {
      // 有值查值
      for (MenuItem item in widget.items) {
        if (item.value == currentValue) {
          label = item.label;
          return;
        }
      }
    }
    // 没值默认取第一个
    if (widget.items.isNotEmpty) {
      label = widget.items[0].label;
    }
  }

  @override
  Widget build(BuildContext context) {
    initTitle();
    return Wrap(
      children: [
        if (widget.title != null)
          Text(widget.title!, style: TextStyle(fontSize: 18)),
        PopupMenuButton<String>(
          // initialValue: currentValue,
          tooltip: widget.tooltip,
          offset: Offset(25, 30),
          enableFeedback: true,
          child: Listener(
            // 使用listener事件能够继续传递
            onPointerDown: (event) {
              setState(() {
                isExpand = !isExpand;
              });
            },
            child: Wrap(
              children: [
                Text(
                  label,
                  style: TextStyle(fontSize: 18),
                ),
                isExpand
                    ? const Icon(Icons.arrow_drop_up)
                    : const Icon(Icons.arrow_drop_down)
              ],
            ),
          ),
          onSelected: (value) {
            widget.valueChanged?.call(value);
            setState(() {
              currentValue = value;
              isExpand = !isExpand;
            });
          },
          onCanceled: () {
            // 取消展开
            setState(() {
              isExpand = false;
            });
          },
          itemBuilder: (context) {
            return widget.items
                .map(
                  (item) => item.value == currentValue
                  ? PopupMenuItem<String>(
                value: item.value,
                child: Text(
                  item.label,
                  style: TextStyle(
                      color: Theme.of(context).primaryColor),
                ),
              )
                  : PopupMenuItem<String>(
                value: item.value,
                child: Text(item.label),
              ),
            ).toList();
          },
        )
      ],
    );
  }
}

(3)测试代码如下:

import 'package:flutter/material.dart';

import '../../../components/select_menu/MenuItem.dart';
import '../../../components/select_menu/SelectWidget.dart';
import '../../../utils/custom_appbar.dart';

class Test extends StatefulWidget {
  _TestState createState() => _TestState();
}

class _TestState extends State<Test> {
  String value = "1";

  /// 下拉选择值改变
  selectChange(value) {
    print("值改变了:$value");
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: customAppbar(
        title: "下拉菜单演示",
      ),
      body: Row(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          SelectWidget(
            items: [
              MenuItem(label: "张飞", value: '1'),
              MenuItem(label: "关羽", value: '2'),
              MenuItem(label: "刘备", value: '3'),
              MenuItem(label: "亚瑟", value: '4'),
              MenuItem(label: "妲己", value: '5'),
              MenuItem(label: "兰陵王", value: '6'),
            ],
            value: value,
            valueChanged: selectChange,
          ),
        ],
      ),
    );
  }
}

(4)运行结果如下: 1681176528016.gif

问题总结

本文主要介绍flutter中实现下拉菜单组件的一个方案,基于PopupMenuButton来进行实现,有兴趣的同学可以进一步深入研究。