是否可以通过按动作按钮动态创建弹出菜单(PopupMenuButton)并在屏幕中间显示此菜单?例如,如何修改标准颤振应用程序以实现此方案:
void _showPopupMenu()
{
// Create and show popup menu
...
}
我设法在解决问题方面取得了一些进展,但仍有问题。这是main.dart的文本。通过单击画布,可以从_handleTapDown(...)调用_showPopupMenu3(context)函数。菜单确实出现,我可以抓住选项,但选择菜单后没有关闭。要关闭菜单,需要按BACK按钮或单击画布。这可能与CANCEL情况相对应。所以问题是: 1)如何在选择项目后关闭菜单(也许它只是菜单属性的一些参数)? 2)应该传递给位置参数的坐标的目的和含义不是很清楚。如何提高点击坐标旁边的菜单?
来源:
import 'package:flutter/material.dart';
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Flutter Demo',
theme: new ThemeData(
// This is the theme of your application.
//
// Try running your application with "flutter run". You'll see the
// application has a blue toolbar. Then, without quitting the app, try
// changing the primarySwatch below to Colors.green and then invoke
// "hot reload" (press "r" in the console where you ran "flutter run",
// or press Run > Flutter Hot Reload in IntelliJ). Notice that the
// counter didn't reset back to zero; the application is not restarted.
primarySwatch: Colors.blue,
),
home: new TouchTestPage(title: 'Flutter Demo Home Page'),
);
}
}
class TouchTestPage extends StatefulWidget {
TouchTestPage({Key key, this.title}) : super(key: key);
// This widget is the home page of your application. It is stateful, meaning
// that it has a State object (defined below) that contains fields that affect
// how it looks.
// This class is the configuration for the state. It holds the values (in this
// case the title) provided by the parent (in this case the App widget) and
// used by the build method of the State. Fields in a Widget subclass are
// always marked "final".
final String title;
@override
_TouchTestPageState createState() => new _TouchTestPageState();
}
class _TouchTestPageState extends State<TouchTestPage> {
@override
Widget build(BuildContext context) {
// This method is rerun every time setState is called, for instance as done
// by the _incrementCounter method above.
//
// The Flutter framework has been optimized to make rerunning build methods
// fast, so that you can just rebuild anything that needs updating rather
// than having to individually change instances of widgets.
return new Scaffold(
appBar: new AppBar(
// Here we take the value from the MyHomePage object that was created by
// the App.build method, and use it to set our appbar title.
title: new Text(widget.title),
),
body: new Container(
decoration: new BoxDecoration(
color: Colors.white70,
gradient: new LinearGradient(
colors: <Color>[Colors.lightBlue, Colors.white30]),
border: new Border.all(
color: Colors.blueGrey,
width: 1.0,
),
),
child: new Center(child: new TouchControl()),
),
);
}
}
class TouchControl extends StatefulWidget {
final double xPos;
final double yPos;
final ValueChanged<Offset> onChanged;
const TouchControl({
Key key,
this.onChanged,
this.xPos: 0.0,
this.yPos: 0.0,
})
: super(key: key);
@override
TouchControlState createState() => new TouchControlState();
}
class TouchControlState extends State<TouchControl> {
double xPos = 0.0;
double yPos = 0.0;
double xStart = 0.0;
double yStart = 0.0;
double _scale = 1.0;
double _prevScale = null;
void reset()
{
xPos = 0.0;
yPos = 0.0;
}
final List<String> popupRoutes = <String>[
"Properties", "Delete", "Leave"
];
String selectedPopupRoute = "Properties";
void _showPopupMenu3(BuildContext context)
{
showMenu<String>(
context: context,
initialValue: selectedPopupRoute,
position: new RelativeRect.fromLTRB(40.0, 60.0, 100.0, 100.0),
items: popupRoutes.map((String popupRoute) {
return new PopupMenuItem<String>(
child: new
ListTile(
leading: const Icon(Icons.visibility),
title: new Text(popupRoute),
onTap: ()
{
setState(()
{
print("onTap [${popupRoute}] ");
selectedPopupRoute = popupRoute;
});
}
),
value: popupRoute,
);
}).toList(),
);
}
void onChanged(Offset offset)
{
final RenderBox referenceBox = context.findRenderObject();
Offset position = referenceBox.globalToLocal(offset);
if (widget.onChanged != null)
{
// print('---- onChanged.CHANGE ----');
widget.onChanged(position);
}
else
{
// print('---- onChanged.NO CHANGE ----');
}
xPos = position.dx;
yPos = position.dy;
}
@override
bool hitTestSelf(Offset position) => true;
void _handlePanStart(DragStartDetails details) {
print('start');
// _scene.clear();
final RenderBox referenceBox = context.findRenderObject();
Offset position = referenceBox.globalToLocal(details.globalPosition);
onChanged(details.globalPosition);
xStart = xPos;
yStart = yPos;
}
void _handlePanEnd(DragEndDetails details) {
print("_handlePanEnd");
print('end');
}
void _handleTapDown(TapDownDetails details) {
print('--- _handleTapDown ---');
final RenderBox referenceBox = context.findRenderObject();
Offset position = referenceBox.globalToLocal(details.globalPosition);
onChanged(new Offset(0.0, 0.0));
_showPopupMenu3(context); //>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
print('+++ _handleTapDown [${position.dx},${position.dy}] +++');
}
void _handleTapUp(TapUpDetails details) {
// _scene.clear();
print('--- _handleTapUp ---');
final RenderBox referenceBox = context.findRenderObject();
Offset position = referenceBox.globalToLocal(details.globalPosition);
onChanged(new Offset(0.0, 0.0));
//_showPopupMenu(context);
print('+++ _handleTapUp [${position.dx},${position.dy}] +++');
}
void _handleDoubleTap() {
print('_handleDoubleTap');
}
void _handleLongPress() {
print('_handleLongPress');
}
void _handlePanUpdate(DragUpdateDetails details) {
// logger.clear("_handlePanUpdate");
final RenderBox referenceBox = context.findRenderObject();
Offset position = referenceBox.globalToLocal(details.globalPosition);
onChanged(details.globalPosition);
}
@override
Widget build(BuildContext context) {
return new ConstrainedBox(
constraints: new BoxConstraints.expand(),
child: new GestureDetector(
behavior: HitTestBehavior.opaque,
onPanStart: _handlePanStart,
onPanEnd: _handlePanEnd,
onPanUpdate: _handlePanUpdate,
onTapDown: _handleTapDown,
onTapUp: _handleTapUp,
onDoubleTap: _handleDoubleTap,
onLongPress: _handleLongPress,
// onScaleStart: _handleScaleStart,
// onScaleUpdate: _handleScaleUpdate,
// onScaleEnd: _handleScaleEnd,
// child: new CustomPaint(
// size: new Size(xPos, yPos),
// painter: new ScenePainter(editor.getScene()),
// foregroundPainter: new TouchControlPainter(/*_scene*//*editor.getScene(),*/ xPos, yPos),
// ),
),
);
}
}
答案 0 :(得分:14)
是可以的
void _showPopupMenu() async {
await showMenu(
context: context,
position: RelativeRect.fromLTRB(100, 100, 100, 100),
items: [
PopupMenuItem(
child: Text("View"),
),
PopupMenuItem(
child: Text("Edit"),
),
PopupMenuItem(
child: Text("Delete"),
),
],
elevation: 8.0,
);
}
有时候,您想在按下按钮的位置显示 _showPopupMenu 为此使用GestureDetector
GestureDetector(
onTapDown: (TapDownDetails details) {
_showPopupMenu(details.globalPosition);
},
child: Container(child: Text("Press Me")),
);
,然后_showPopupMenu将类似于
_showPopupMenu(Offset offset) async {
double left = offset.dx;
double top = offset.dy;
await showMenu(
context: context,
position: RelativeRect.fromLTRB(left, top, 0, 0),
items: [
...,
elevation: 8.0,
);
}
答案 1 :(得分:1)
@Vishal Singh的回答非常有帮助。但是,我有一个问题,就是菜单总是在右边。给正确的值一个很高的价值就可以解决它, 示例:
_showPopupMenu(Offset position) async {
await showMenu(
context: context,
position: RelativeRect.fromLTRB(position.dx, position.dy, 100000, 0),
...
答案 2 :(得分:1)
@Vishal Singh 的回答需要两个改进:
0
用于 right
,则菜单会向右对齐,因为在水平方向上,菜单的位置使其向空间最大的方向增长。例如,如果位置描述屏幕左边缘的矩形,则菜单左边缘与位置左边缘对齐,菜单向右增长。
0
用于 bottom
,这适用于没有 initialValue
的弹出菜单,但如果设置了 initialValue
,则将菜单向下移动。这是因为如果指定了 initialValue,则具有匹配值的第一个项目将被突出显示,并且 position 的值给出其垂直中心将与突出显示项目的垂直中心对齐的矩形(如果可能)。
<块引用>如果没有指定initialValue,则菜单的顶部将与位置矩形的顶部对齐。
https://api.flutter.dev/flutter/material/showMenu.html
因此,为了获得更通用的解决方案,请正确计算右侧和底部:
final screenSize = MediaQuery.of(context).size;
showMenu(
context: context,
position: RelativeRect.fromLTRB(
offset.dx,
offset.dy,
screenSize.width - offset.dx,
screenSize.height - offset.dy,
),
items: [
// ...
],
);
答案 3 :(得分:0)
关闭菜单很简单:需要添加一行:Navigator.pop(context);
onTap: ()
{
setState(()
{
print("onTap [${popupRoute}] ");
selectedPopupRoute = popupRoute;
Navigator.pop(context);
});
}
仍然以坐标问题。