OP和第一个回答者使用
PopupMenuButton
在他们的案件中效果很好。但我认为更普遍的问题是如何定位自己的菜单以及如何接收用户的响应
不使用
弹出按钮
值得回答,因为有时我们需要一个自定义小部件上的弹出菜单,我们希望它出现在一些手势上,而不是简单的点击(例如,操作的初衷是长按)。
我开始制作一个简单的应用程序,演示以下内容:
-
使用A
GestureDetector
捕捉长按
-
使用函数
showMenu()
显示弹出菜单,并将其放置在手指触摸附近
-
如何接收用户的选择
-
(奖金)如何使
PopupMenuEntry
表示多个值(经常使用
PopupMenuItem
只能表示一个值)
结果是,当您长按一个大的黄色区域时,会出现一个弹出菜单,您可以在上面选择
+1
或
-1
大的数字会相应地增加或减少:
跳到整个代码体的结尾。我的评论被撒在那里解释我在做什么。以下是需要注意的几点:
-
showMenu()
的
position
参数需要一些努力才能理解。这是一个
RelativeRect
,它表示较小的矩形如何定位在较大的矩形内。在我们的例子中,较大的矩形区域是整个屏幕,较小的矩形区域是触摸区域。flutter根据这些规则定位弹出菜单(纯英语):
-
如果小直肠向
左边
大矩形的一半,弹出菜单将与
较小矩形的左边缘
-
如果小直肠向
正确的
大矩形的一半,弹出菜单将与
小矩形的右边缘
-
如果较小的rect在中间,哪边赢取决于语言的文本方向。
左边缘
如果使用英语和其他从左到右的语言,则赢,否则赢。
参考总是有用的
PopupMenuButton
's official implementation
看看它是怎么用的
显示菜单()
显示菜单。
-
显示菜单()
返回A
Future
. 使用
Future.then()
注册回调以处理用户选择。另一个选择是使用
await
.
-
记住
PopupMenuEntry
是(的子类)
StatefulWidget
. 您可以在其中布局任意数量的子部件。这是如何在
弹出菜单
. 如果您想让它代表两个值,只需让它包含两个按钮,不管您想如何布局它们。
-
要关闭弹出菜单,请使用
Navigator.pop()
. flutter将弹出菜单视为一个较小的“页面”。当我们显示一个弹出菜单时,实际上是将一个“页面”推到导航器的堆栈中。为了关闭弹出菜单,我们从堆栈中弹出它,从而完成前面提到的
未来
.
以下是完整代码:
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Popup Menu Usage',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Popup Menu Usage'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
var _count = 0;
var _tapPosition;
void _showCustomMenu() {
final RenderBox overlay = Overlay.of(context).context.findRenderObject();
showMenu(
context: context,
items: <PopupMenuEntry<int>>[PlusMinusEntry()],
position: RelativeRect.fromRect(
_tapPosition & Size(40, 40), // smaller rect, the touch area
Offset.zero & overlay.size // Bigger rect, the entire screen
)
)
// This is how you handle user selection
.then<void>((int delta) {
// delta would be null if user taps on outside the popup menu
// (causing it to close without making selection)
if (delta == null) return;
setState(() {
_count = _count + delta;
});
});
// Another option:
//
// final delta = await showMenu(...);
//
// Then process `delta` however you want.
// Remember to make the surrounding function `async`, that is:
//
// void _showCustomMenu() async { ... }
}
void _storePosition(TapDownDetails details) {
_tapPosition = details.globalPosition;
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
GestureDetector(
// This does not give the tap position ...
onLongPress: _showCustomMenu,
// Have to remember it on tap-down.
onTapDown: _storePosition,
child: Container(
color: Colors.amberAccent,
padding: const EdgeInsets.all(100.0),
child: Text(
'$_count',
style: const TextStyle(
fontSize: 100, fontWeight: FontWeight.bold),
),
),
),
],
),
),
);
}
}
class PlusMinusEntry extends PopupMenuEntry<int> {
@override
double height = 100;
// height doesn't matter, as long as we are not giving
// initialValue to showMenu().
@override
bool represents(int n) => n == 1 || n == -1;
@override
PlusMinusEntryState createState() => PlusMinusEntryState();
}
class PlusMinusEntryState extends State<PlusMinusEntry> {
void _plus1() {
// This is how you close the popup menu and return user selection.
Navigator.pop<int>(context, 1);
}
void _minus1() {
Navigator.pop<int>(context, -1);
}
@override
Widget build(BuildContext context) {
return Row(
children: <Widget>[
Expanded(child: FlatButton(onPressed: _plus1, child: Text('+1'))),
Expanded(child: FlatButton(onPressed: _minus1, child: Text('-1'))),
],
);
}
}