一、需求来源
研究源码(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(),
);
}
);
}
}
四、总结
1、使用 MenuAnchor 组件需要注意只进行局部刷新,不然会有性能损耗;使用demo 中 用 StatefulBuilder 做了约束;
2、NMenuAnchor 只是简易封装,方便代码复用,大家可以根据项目需要进行扩展;
3、NMenuAnchor 用 cbName 函数进行标题显示,极大的扩展了适用范围;不需要额外的虚拟类或者类协议类进行标题属性限制;任何对象的任何属性可以在此函数中处理返回显示即可;
arduino
/// 标题回调
final String Function(E e) cbName;
4、MenuAnchor 是下拉菜单 DropMenu 的最佳实践,下拉菜单却不是它的能力边界;它还可以实现下拉树形菜单的效果;效果类似 mac 右上角顶部菜单栏;
相关组件还有: MenuBar MenuAcceleratorLabel RadioMenuButton CheckboxMenuButton SubmenuButton