Flutter 封装:最佳实践 —— 下拉菜单 MenuAnchor

一、需求来源

研究源码(Flutter 3.10.6)时发现了 MenuAnchor 组件,下拉菜单的最佳实践;

效果如下:

二、使用示例

scala 复制代码
import 'package:flutter/material.dart';
import 'package:flutter_templet_project/basicWidget/n_menu_anchor.dart';


enum SomeItemType { none, itemOne, itemTwo, itemThree }


class MenuAnchorDemo extends StatefulWidget {
  const MenuAnchorDemo({super.key});

  @override
  State<MenuAnchorDemo> createState() => _MenuAnchorDemoState();
}

class _MenuAnchorDemoState extends State<MenuAnchorDemo> {

  final _selectedItemVN = ValueNotifier<SomeItemType>(SomeItemType.none);

  String defaultValue = "-";

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('$widget')),
      body: buildBody(),
      // body: CheckboxMenuDemo(message: kMessage,),
    );
  }

  buildBody() {
    return Column(
      children: [
        // Spacer(),
        ValueListenableBuilder(
          valueListenable: _selectedItemVN,
          builder: (context, value, child){

            return Text(value.name ?? defaultValue);
          }
        ),
        // 直接使用
        // buildMenuAnchor<SomeItemType>(
        //   values: SomeItemType.values,
        //   initialItem: SomeItemType.itemThree,
        //   cbName: (e) => e.name,
        //   onChanged: (SomeItemType e) {
        //     debugPrint(e.name);
        //     _selectedItemVN.value = e;
        //   },
        // ),

        // 简单封装
        NMenuAnchor<SomeItemType>(
          values: SomeItemType.values,
          initialItem: SomeItemType.itemThree,
          cbName: (e) => e.name,
          onChanged: (SomeItemType e) {
            debugPrint(e.name);
            _selectedItemVN.value = e;
          },
        ),

      ],
    );
  }

  buildMenuAnchor<E>({
    required List<E> values, 
    required E initialItem,
    required String Function(E e) cbName,
    required ValueChanged<E> onChanged,
    Widget Function(MenuController controller, E? selectedItem)? itemBuilder,
  }) {
    var selectedItem = initialItem;

    return StatefulBuilder(
      builder: (BuildContext context, StateSetter setState) {

        return MenuAnchor(
          alignmentOffset: Offset(0, 0),
          builder: (context, MenuController controller, Widget? child) {

            return itemBuilder?.call(controller, selectedItem) ?? OutlinedButton(
              onPressed: (){
                if (controller.isOpen) {
                  controller.close();
                } else {
                  controller.open();
                }
              },
              child: Row(
                mainAxisSize: MainAxisSize.min,
                children: [
                  Text(cbName(selectedItem)),
                  Icon(Icons.arrow_drop_down),
                ],
              ),
            );
          },
          menuChildren: values.map((e) {
            return MenuItemButton(
              onPressed: () {
                selectedItem = e;
                setState(() {});
                onChanged.call(e);
              },
              child: Text(cbName(e)),
            );
          }).toList(),
        );
      }
    );
  }
}

三、源码

1、NMenuAnchor 源码

kotlin 复制代码
//
//  NEnumMenuAnchor.dart
//  flutter_templet_project
//
//  Created by shang on 2023/10/31 19:29.
//  Copyright © 2023/10/31 shang. All rights reserved.
//


import 'package:flutter/material.dart';

/// MenuAnchor 简易封装,方便代码复用
class NMenuAnchor<E> extends StatelessWidget {

  NMenuAnchor({
    super.key,
    required this.values,
    // this.selectedItem,
    required this.initialItem,
    this.itemBuilder,
    required this.onChanged,
    required this.cbName,
  });
  /// 数据源
  final List<E> values;
  // E? selectedItem;
  /// 初始化数据
  final E initialItem;
  /// item 子视图构建器
  final Widget Function(MenuController controller, E? selectedItem)? itemBuilder;
  /// 选择回调
  final ValueChanged<E> onChanged;
  /// 标题回调
  final String Function(E e) cbName;


  @override
  Widget build(BuildContext context) {
    var selectedItem = initialItem;

    return StatefulBuilder(
        builder: (BuildContext context, StateSetter setState) {

          return MenuAnchor(
            builder: (context, MenuController controller, Widget? child) {

              return itemBuilder?.call(controller, selectedItem) ?? OutlinedButton(
                style: OutlinedButton.styleFrom(
                  // backgroundColor: Color(0xff5690F4).withOpacity(0.1),
                  // foregroundColor: Color(0xff5690F4),
                  elevation: 0,
                  // shape: StadiumBorder(),
                  tapTargetSize: MaterialTapTargetSize.shrinkWrap,
                  // minimumSize: Size(64, 32),
                  padding: EdgeInsets.only(left: 8, right: 2, top: 6, bottom: 6),
                  foregroundColor: Colors.black87,
                ),
                onPressed: (){
                  if (controller.isOpen) {
                    controller.close();
                  } else {
                    controller.open();
                  }
                },
                child: Row(
                  mainAxisSize: MainAxisSize.min,
                  children: [
                    Text(cbName(selectedItem)),
                    Icon(Icons.arrow_drop_down),
                  ],
                ),
              );
            },
            menuChildren: values.map((e) {
              return MenuItemButton(
                onPressed: () {
                  selectedItem = e;
                  setState(() {});
                  onChanged.call(e);
                },
                child: Text(cbName(e)),
              );
            }).toList(),
          );
        }
    );
  }
}

四、总结

2、NMenuAnchor 只是简易封装,方便代码复用,大家可以根据项目需要进行扩展;
3、NMenuAnchor 用 cbName 函数进行标题显示,极大的扩展了适用范围;不需要额外的虚拟类或者类协议类进行标题属性限制;任何对象的任何属性可以在此函数中处理返回显示即可;
arduino 复制代码
  /// 标题回调
  final String Function(E e) cbName;
4、MenuAnchor 是下拉菜单 DropMenu 的最佳实践,下拉菜单却不是它的能力边界;它还可以实现下拉树形菜单的效果;效果类似 mac 右上角顶部菜单栏;

相关组件还有: MenuBar MenuAcceleratorLabel RadioMenuButton CheckboxMenuButton SubmenuButton

相关推荐
学习使我快乐012 小时前
JS进阶 3——深入面向对象、原型
开发语言·前端·javascript
bobostudio19952 小时前
TypeScript 设计模式之【策略模式】
前端·javascript·设计模式·typescript·策略模式
黄尚圈圈3 小时前
Vue 中引入 ECharts 的详细步骤与示例
前端·vue.js·echarts
浮华似水4 小时前
简洁之道 - React Hook Form
前端
正小安6 小时前
如何在微信小程序中实现分包加载和预下载
前端·微信小程序·小程序
_.Switch7 小时前
Python Web 应用中的 API 网关集成与优化
开发语言·前端·后端·python·架构·log4j
一路向前的月光7 小时前
Vue2中的监听和计算属性的区别
前端·javascript·vue.js
长路 ㅤ   7 小时前
vite学习教程06、vite.config.js配置
前端·vite配置·端口设置·本地开发
长路 ㅤ   7 小时前
vue-live2d看板娘集成方案设计使用教程
前端·javascript·vue.js·live2d
Fan_web7 小时前
jQuery——事件委托
开发语言·前端·javascript·css·jquery