在Flutter中提供了两种方式来完成弹出菜单功能。

PopupMenuButton

https://api.flutter.dev/flutter/material/PopupMenuButton-class.html
菜单隐藏式,点击或调用onSelected时显示一个弹出式菜单列表。

PopupMenuButton定义

  1. PopupMenuButton({
  2. Key key,
  3. @required this.itemBuilder,
  4. this.initialValue, //初始值,设置初始值后,打开菜单后,设置的值将会高亮,
  5. this.onSelected, //选取后回调
  6. this.onCanceled, //popup菜单 取消选取回调
  7. this.tooltip, //入口按钮上长按提示
  8. this.enabled = true, //是否开启 popup
  9. this.icon, //icon 与 child 只能用一个
  10. this.child,
  11. // 默认情况下,PopupMenuButton显示3个小圆点,我们也可以对其进行自定义设置。
  12. // 设置后,child组件将会被InkWell包裹,点击弹出菜单。
  13. this.color, //popup背景色
  14. this.elevation, //popup 阴影
  15. this.shape, //popup边框形状
  16. this.padding = const EdgeInsets.all(8.0),
  17. this.offset = Offset.zero, //弹出菜单偏移量
  18. //offset: Offset(10.0, 20.0), popup距左10,距右20,值超出entry右下角位置时,按右下角算。
  19. this.captureInheritedThemes = true,
  20. })

PopupMenuItem 定义

  1. PopupMenuItem({
  2. Key key,
  3. this.value, //当此项选中后,此值将会通过onSelected返回
  4. this.enabled = true, //此项是否可用
  5. this.height = kMinInteractiveDimension, //此项的高度
  6. this.textStyle,
  7. this.mouseCursor,
  8. @required this.child,
  9. })

示例1:

asdf.gif

  1. import "package:flutter/material.dart";
  2. import 'package:app1/coms/base/empty_null/empty_null.dart';
  3. class TestPage extends StatefulWidget {
  4. @override
  5. _TestPageState createState() => _TestPageState();
  6. }
  7. class _TestPageState extends State<TestPage> {
  8. List<Book> books = [
  9. Book('1', '时间简史'),
  10. Book('2', '漂'),
  11. Book('3', '茶花女'),
  12. Book('4', '存在与虚无'),
  13. ];
  14. Book _selected;
  15. @override
  16. void initState() {
  17. super.initState();
  18. _selected = books[0];
  19. }
  20. @override
  21. Widget build(BuildContext context) {
  22. return Scaffold(
  23. appBar: EmptyNull.appBar(),
  24. body: PopupMenuButton(
  25. // 入口entry
  26. child: Container(
  27. width: 100,
  28. height: 50,
  29. color: Colors.grey,
  30. child: Text(_selected.label),
  31. ),
  32. // 弹出菜单选中项 高亮
  33. initialValue: _selected,
  34. // 构建弹出菜单
  35. // PopupMenuButton的每一项都需要是PopupMenuEntry类型,PopupMenuEntry为抽象类,
  36. // 其子类有PopupMenuItem、PopupMenuDivider、CheckedPopupMenuItem。
  37. itemBuilder: (BuildContext context) => <PopupMenuEntry<Book>>[
  38. for (var i = 0; i < books.length; i++)
  39. PopupMenuItem<Book>(
  40. value: books[i],
  41. child: Text(books[i].label),
  42. ),
  43. ],
  44. // 弹出菜单子项点击回调
  45. onSelected: (value) {
  46. setState(() {
  47. _selected = value;
  48. });
  49. },
  50. // 弹出菜单偏移量
  51. offset: Offset(20.0, 20.0),
  52. shape: RoundedRectangleBorder(
  53. side: BorderSide(color: Colors.red),
  54. borderRadius: BorderRadius.all(Radius.circular(10.0)),
  55. ),
  56. ),
  57. );
  58. }
  59. }
  60. class Book {
  61. Book(this.value, this.label);
  62. final String value;
  63. final String label;
  64. }


PopupMenuDivider

PopupMenuDivider是菜单分割线,定义如下:

  1. PopupMenuDivider({
  2. Key key,
  3. this.height = _kMenuDividerHeight,
  4. // PopupMenuDivider默认高度为16,注意这个高度并不是分割线的高度,而是分割线控件的高度,
  5. // 设置为50代码:PopupMenuDivider(height: 50,),
  6. })

示例:

弹出菜单 - 图2

  1. PopupMenuButton<String>(
  2. itemBuilder: (context) {
  3. return <PopupMenuEntry<String>>[
  4. PopupMenuItem<String>(value: '语文', child: Text('语文')),
  5. PopupMenuDivider(),
  6. PopupMenuItem<String>(value: '数学', child: Text('数学')),
  7. ];
  8. },
  9. )

CheckedPopupMenuItem

前面带选中的控件。
image.png

  1. CheckedPopupMenuItem<Book>(
  2. value: books[i],
  3. checked: _selected == books[i], //是否选中。为true时,前边带上对勾图标
  4. child: Text(books[i].label),
  5. ),

showMenu

PopupMenuButton也是使用showMenu实现的。
image.png

  1. class _TestPageState extends State<TestPage> {
  2. openMenu() {
  3. showMenu(
  4. context: context,
  5. position: RelativeRect.fill, //表示弹出的位置
  6. items: <PopupMenuEntry>[
  7. PopupMenuItem(child: Text('语文')),
  8. CheckedPopupMenuItem(
  9. child: Text('数学'),
  10. checked: true,
  11. ),
  12. PopupMenuItem(child: Text('英语')),
  13. ],
  14. );
  15. }
  16. @override
  17. Widget build(BuildContext context) {
  18. return Scaffold(
  19. appBar: EmptyNull.appBar(),
  20. body: RaisedButton(
  21. child: Text('点击我'),
  22. onPressed: openMenu,
  23. ),
  24. );
  25. }
  26. }

属性和PopupMenuButton基本一样,但使用showMenu需要我们指定位置,所以一般情况下,我们不会直接使用showMenu,而是使用PopupMenuButton,免去了计算位置的过程。

PopupMenuButton是如何计算的?

  1. final PopupMenuThemeData popupMenuTheme = PopupMenuTheme.of(context);
  2. final RenderBox button = context.findRenderObject();
  3. final RenderBox overlay = Overlay.of(context).context.findRenderObject();
  4. final RelativeRect position = RelativeRect.fromRect(
  5. Rect.fromPoints(
  6. button.localToGlobal(widget.offset, ancestor: overlay),
  7. button.localToGlobal(button.size.bottomRight(Offset.zero), ancestor: overlay),
  8. ),
  9. Offset.zero & overlay.size,
  10. );
  11. final List<PopupMenuEntry<T>> items = widget.itemBuilder(context);