是否可以通过按下操作按钮动态创建弹出菜单(PopupMenuButton)并在屏幕中间显示此菜单?例如,如何修改标准Flutter应用程序以实现此场景:
void _showPopupMenu()
{
// Create and show popup menu
...
}
我设法在解决问题方面取得了一些进展,但仍然存在问题。下面是main.dart的文本。通过单击画布,从_handleTapDown(...)调用_showPopupMenu3(context)函数。菜单确实出现,我可以捕捉选项,但选择菜单后没有关闭。关闭菜单需要按下后退按钮或点击画布上。这可能对应于CANCEL情况。所以问题是:1)如何在选择项目后关闭菜单(可能只是菜单属性的某个参数)?2)应该传递给position参数的坐标的目的和含义不太清楚。如何提升单击坐标旁边的菜单?
来源:
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),
// ),
),
);
}
}
6条答案
按热度按时间ax6ht2ek1#
是的有可能
有时候,您可能希望在按下按钮的位置显示_showPopupMenu**,请使用GestureDetector
然后_showPopupMenu会像这样
xggvc2p62#
@Vishal Singh的回答需要两个改进:
1.如果使用
0
作为right
,则菜单将向右对齐,因为在水平方向上,菜单的位置使其在具有最大空间的方向上增长。例如,如果位置描述了屏幕左边缘上的矩形,则菜单的左边缘与位置的左边缘对齐,并且菜单向右增长。
1.如果您对
bottom
使用0
,则在没有initialValue
的弹出菜单中可以正常工作,但如果设置了initialValue
,则会将菜单向下移动。这是因为如果指定了initialValue,那么第一个具有匹配值的项目将被突出显示,并且position的值给出了一个矩形,其垂直中心将与突出显示的项目的垂直中心对齐(如果可能)。
如果没有指定initialValue,那么菜单的顶部将与位置矩形的顶部对齐。
https://api.flutter.dev/flutter/material/showMenu.html
因此,为了更通用的解决方案,正确计算右侧和底部:
jv4diomz3#
@Vishal Singh的回答真的很有帮助。然而,我有一个问题,菜单总是在右边。给正确的值一个非常高的值修复它,示例:
x4shl7ld4#
自定义单击时的弹出菜单
使用async方法和showMenu()小部件
你调用popup的小部件代码
h43kikqp5#
除了Vishal Singh的答案之外,如果你不想让菜单出现在与你点击的偏移量相关的位置,而是与你覆盖的构建函数返回的小部件(你用作菜单打开器的那个)相关,使用你的上下文来找到它的渲染对象,然后将它的位置传递给你的showMenu函数position属性。
vof42yt16#
关闭菜单很简单:需要添加一行:pop(context);
坐标仍有问题。