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

相关推荐
懒大王爱吃狼39 分钟前
Python教程:python枚举类定义和使用
开发语言·前端·javascript·python·python基础·python编程·python书籍
逐·風5 小时前
unity关于自定义渲染、内存管理、性能调优、复杂物理模拟、并行计算以及插件开发
前端·unity·c#
Devil枫5 小时前
Vue 3 单元测试与E2E测试
前端·vue.js·单元测试
尚梦6 小时前
uni-app 封装刘海状态栏(适用小程序, h5, 头条小程序)
前端·小程序·uni-app
GIS程序媛—椰子6 小时前
【Vue 全家桶】6、vue-router 路由(更新中)
前端·vue.js
前端青山7 小时前
Node.js-增强 API 安全性和性能优化
开发语言·前端·javascript·性能优化·前端框架·node.js
毕业设计制作和分享7 小时前
ssm《数据库系统原理》课程平台的设计与实现+vue
前端·数据库·vue.js·oracle·mybatis
lqj_本人8 小时前
鸿蒙next选择 Flutter 开发跨平台应用的原因
flutter·华为·harmonyos
清灵xmf9 小时前
在 Vue 中实现与优化轮询技术
前端·javascript·vue·轮询
大佩梨9 小时前
VUE+Vite之环境文件配置及使用环境变量
前端