如何动态创建并显示一个弹出菜单?

时间:2018-06-08 10:01:10

标签: popup contextmenu flutter

是否可以通过按动作按钮动态创建弹出菜单(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),
//        ),
      ),
    );
  }
}

4 个答案:

答案 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 的回答需要两个改进:

  1. 如果您将 0 用于 right,则菜单会向右对齐,因为
<块引用>

在水平方向上,菜单的位置使其向空间最大的方向增长。例如,如果位置描述屏幕左边缘的矩形,则菜单左边缘与位置左边缘对齐,菜单向右增长。

  1. 如果您将 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);
        });
      }

仍然以坐标问题。