popup - 如何在颤动中动态创建和显示弹出菜单?
问题描述
是否可以通过按下操作按钮来动态创建弹出菜单(PopupMenuButton)并将此菜单显示在屏幕中间?例如,如何修改标准的 Flutter 应用来实现这个场景:
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),
// ),
),
);
}
}
解决方案
对的,这是可能的
void _showPopupMenu() async {
await showMenu(
context: context,
position: RelativeRect.fromLTRB(100, 100, 100, 100),
items: [
PopupMenuItem(
value: 1
child: Text("View"),
),
PopupMenuItem(
value: 2
child: Text("Edit"),
),
PopupMenuItem(
value: 3
child: Text("Delete"),
),
],
elevation: 8.0,
).then((value){
// NOTE: even you didnt select item this method will be called with null of value so you should call your call back with checking if value is not null
if(value!=null)
print(value);
});
}
有时您希望在按下按钮 的位置显示_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,
);
}
推荐阅读
- laravel - 为什么我在 laravel 仪表板中收到此页面无法正常工作目前无法处理此请求 HTTP ERROR 500?
- mysql - dockerize mysql 网络应用程序
- c++ - 原生 v8::Promise 结果
- javascript - 带有可观察和数组的茉莉花测试
- c# - 带有 IdentityDbContext 和 hasData 播种的 EFcore 创建 IdentityRole
- pyspark - PySpark DataFrame change column of string to array before using explode
- maven - 无法使用maven通过intellij-idea构建项目,但通过控制台命令“mvn package”构建成功
- android - 不通过子句“in”删除对象列表
- android - Android Listview在滚动时重复项目
- javascript - 在 mvc (.cshtml) 中上传之前预览多个图像