在Flutter中提供了两种方式来完成弹出菜单功能。
PopupMenuButton
https://api.flutter.dev/flutter/material/PopupMenuButton-class.html
菜单隐藏式,点击或调用onSelected时显示一个弹出式菜单列表。
PopupMenuButton定义
PopupMenuButton({
Key key,
@required this.itemBuilder,
this.initialValue, //初始值,设置初始值后,打开菜单后,设置的值将会高亮,
this.onSelected, //选取后回调
this.onCanceled, //popup菜单 取消选取回调
this.tooltip, //入口按钮上长按提示
this.enabled = true, //是否开启 popup
this.icon, //icon 与 child 只能用一个
this.child,
// 默认情况下,PopupMenuButton显示3个小圆点,我们也可以对其进行自定义设置。
// 设置后,child组件将会被InkWell包裹,点击弹出菜单。
this.color, //popup背景色
this.elevation, //popup 阴影
this.shape, //popup边框形状
this.padding = const EdgeInsets.all(8.0),
this.offset = Offset.zero, //弹出菜单偏移量
//offset: Offset(10.0, 20.0), popup距左10,距右20,值超出entry右下角位置时,按右下角算。
this.captureInheritedThemes = true,
})
PopupMenuItem 定义
PopupMenuItem({
Key key,
this.value, //当此项选中后,此值将会通过onSelected返回
this.enabled = true, //此项是否可用
this.height = kMinInteractiveDimension, //此项的高度
this.textStyle,
this.mouseCursor,
@required this.child,
})
示例1:
import "package:flutter/material.dart";
import 'package:app1/coms/base/empty_null/empty_null.dart';
class TestPage extends StatefulWidget {
@override
_TestPageState createState() => _TestPageState();
}
class _TestPageState extends State<TestPage> {
List<Book> books = [
Book('1', '时间简史'),
Book('2', '漂'),
Book('3', '茶花女'),
Book('4', '存在与虚无'),
];
Book _selected;
@override
void initState() {
super.initState();
_selected = books[0];
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: EmptyNull.appBar(),
body: PopupMenuButton(
// 入口entry
child: Container(
width: 100,
height: 50,
color: Colors.grey,
child: Text(_selected.label),
),
// 弹出菜单选中项 高亮
initialValue: _selected,
// 构建弹出菜单
// PopupMenuButton的每一项都需要是PopupMenuEntry类型,PopupMenuEntry为抽象类,
// 其子类有PopupMenuItem、PopupMenuDivider、CheckedPopupMenuItem。
itemBuilder: (BuildContext context) => <PopupMenuEntry<Book>>[
for (var i = 0; i < books.length; i++)
PopupMenuItem<Book>(
value: books[i],
child: Text(books[i].label),
),
],
// 弹出菜单子项点击回调
onSelected: (value) {
setState(() {
_selected = value;
});
},
// 弹出菜单偏移量
offset: Offset(20.0, 20.0),
shape: RoundedRectangleBorder(
side: BorderSide(color: Colors.red),
borderRadius: BorderRadius.all(Radius.circular(10.0)),
),
),
);
}
}
class Book {
Book(this.value, this.label);
final String value;
final String label;
}
PopupMenuDivider
PopupMenuDivider是菜单分割线,定义如下:
PopupMenuDivider({
Key key,
this.height = _kMenuDividerHeight,
// PopupMenuDivider默认高度为16,注意这个高度并不是分割线的高度,而是分割线控件的高度,
// 设置为50代码:PopupMenuDivider(height: 50,),
})
示例:
PopupMenuButton<String>(
itemBuilder: (context) {
return <PopupMenuEntry<String>>[
PopupMenuItem<String>(value: '语文', child: Text('语文')),
PopupMenuDivider(),
PopupMenuItem<String>(value: '数学', child: Text('数学')),
];
},
)
CheckedPopupMenuItem
前面带选中的控件。
CheckedPopupMenuItem<Book>(
value: books[i],
checked: _selected == books[i], //是否选中。为true时,前边带上对勾图标
child: Text(books[i].label),
),
showMenu
PopupMenuButton也是使用showMenu实现的。
class _TestPageState extends State<TestPage> {
openMenu() {
showMenu(
context: context,
position: RelativeRect.fill, //表示弹出的位置
items: <PopupMenuEntry>[
PopupMenuItem(child: Text('语文')),
CheckedPopupMenuItem(
child: Text('数学'),
checked: true,
),
PopupMenuItem(child: Text('英语')),
],
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: EmptyNull.appBar(),
body: RaisedButton(
child: Text('点击我'),
onPressed: openMenu,
),
);
}
}
属性和PopupMenuButton
基本一样,但使用showMenu
需要我们指定位置,所以一般情况下,我们不会直接使用showMenu
,而是使用PopupMenuButton
,免去了计算位置的过程。
PopupMenuButton
是如何计算的?
final PopupMenuThemeData popupMenuTheme = PopupMenuTheme.of(context);
final RenderBox button = context.findRenderObject();
final RenderBox overlay = Overlay.of(context).context.findRenderObject();
final RelativeRect position = RelativeRect.fromRect(
Rect.fromPoints(
button.localToGlobal(widget.offset, ancestor: overlay),
button.localToGlobal(button.size.bottomRight(Offset.zero), ancestor: overlay),
),
Offset.zero & overlay.size,
);
final List<PopupMenuEntry<T>> items = widget.itemBuilder(context);