Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How dynamically create and show a popup menu in flutter?

Is it possible to dynamically create a popup menu (PopupMenuButton) by pressing the action button and display this menu in the middle of the screen? For example, how modify for standard flutter application to implement this scenario:

  void _showPopupMenu()
  {
  // Create and show popup menu 
     ...
  }

I managed to advance somewhat in solving the problem, but still there are questions. Here is the text of main.dart. By clicking on the canvas, the _showPopupMenu3 (context) function is called from _handleTapDown (...). The menu does appear, I can catch option, but after selecting the menu isn't closed. To close menu need to press the BACK button or click on the canvas. This probably corresponds to the CANCEL situation. So the questions are: 1) How to close the menu after selecting of item (maybe it's just some parameter of property of menu)? 2) The purpose and meaning of the coordinates that should be passed to the position parameter is not quite clear. How do I raise the menu next to the click coordinate?

Sources:

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),
//        ),
      ),
    );
  }
}
like image 262
Michael Kanzieper Avatar asked Jun 08 '18 10:06

Michael Kanzieper


1 Answers

Yes it is possible

    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);

       });
    }

There will be times when you would want to display _showPopupMenu at the location where you pressed on the button Use GestureDetector for that

GestureDetector(
  onTapDown: (TapDownDetails details) {
    _showPopupMenu(details.globalPosition);
  },
  child: Container(child: Text("Press Me")),
);

and then _showPopupMenu will be like

_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,
  );
}
like image 116
Vishal Singh Avatar answered Sep 18 '22 14:09

Vishal Singh