【问题标题】:How to show a menu at press/finger/mouse/cursor position in flutter如何在颤动的按下/手指/鼠标/光标位置显示菜单
【发布时间】:2019-06-14 06:31:57
【问题描述】:

我有这段从Style clipboard in flutter得到的代码

showMenu(
        context: context,
        // TODO: Position dynamically based on cursor or textfield
        position: RelativeRect.fromLTRB(0.0, 600.0, 300.0, 0.0),
        items: [
          PopupMenuItem(
            child: Row(
              children: <Widget>[
                // TODO: Dynamic items / handle click
                PopupMenuItem(
                  child: Text(
                    "Paste",
                    style: Theme.of(context)
                        .textTheme
                        .body2
                        .copyWith(color: Colors.red),
                  ),
                ),
                PopupMenuItem(
                  child: Text("Select All"),
                ),
              ],
            ),
          ),
        ],
      );

此代码效果很好,除了创建的弹出窗口位于固定位置之外,我将如何使其弹出在鼠标/按下/手指/光标位置或附近的某个位置,有点像当您想在手机上复制和粘贴。 (此对话框弹出不会用于复制和粘贴)

【问题讨论】:

    标签: dart flutter flutter-layout


    【解决方案1】:

    像这样使用手势检测器的 onTapDown

     GestureDetector(
            onTapDown: (TapDownDetails details) {
              showPopUpMenu(details.globalPosition);
            },
    

    然后在这种方法中,我们使用点击详细信息来查找位置

    Future<void> showPopUpMenu(Offset globalPosition) async {
    double left = globalPosition.dx;
    double top = globalPosition.dy;
    await showMenu(
      color: Colors.white,
      //add your color
      context: context,
      position: RelativeRect.fromLTRB(left, top, 0, 0),
      items: [
        PopupMenuItem(
          value: 1,
          child: Padding(
            padding: const EdgeInsets.only(left: 0, right: 40),
            child: Row(
              children: [
                Icon(Icons.mail_outline),
                SizedBox(
                  width: 10,
                ),
                Text(
                  "Menu 1",
                  style: TextStyle(color: Colors.black),
                ),
              ],
            ),
          ),
        ),
        PopupMenuItem(
          value: 2,
          child: Padding(
            padding: const EdgeInsets.only(left: 0, right: 40),
            child: Row(
              children: [
                Icon(Icons.vpn_key),
                SizedBox(
                  width: 10,
                ),
                Text(
                  "Menu 2",
                  style: TextStyle(color: Colors.black),
                ),
              ],
            ),
          ),
        ),
        PopupMenuItem(
          value: 3,
          child: Row(
            children: [
              Icon(Icons.power_settings_new_sharp),
              SizedBox(
                width: 10,
              ),
              Text(
                "Menu 3",
                style: TextStyle(color: Colors.black),
              ),
            ],
          ),
        ),
      ],
      elevation: 8.0,
    ).then((value) {
      print(value);
      if (value == 1) {
        //do your task here for menu 1
      }
      if (value == 2) {
        //do your task here for menu 2
      }
      if (value == 3) {
        //do your task here for menu 3
      }
    });
    

    希望有效果

    【讨论】:

      【解决方案2】:

      我可以通过使用这个答案来解决类似的问题: https://stackoverflow.com/a/54714628/559525

      基本上,我在每个 ListTile 周围添加了一个 GestureDetector(),然后您使用 onTapDown 存储您的新闻位置并使用 onLongPress 调用您的 showMenu 函数。以下是我添加的关键函数:

        _showPopupMenu() async {
          final RenderBox overlay = Overlay.of(context).context.findRenderObject();
      
          await showMenu(
            context: context,
            position: RelativeRect.fromRect(
                _tapPosition & Size(40, 40), // smaller rect, the touch area
                Offset.zero & overlay.size // Bigger rect, the entire screen
                ),
            items: [
              PopupMenuItem(
                child: Text("Show Usage"),
              ),
              PopupMenuItem(
                child: Text("Delete"),
              ),
            ],
            elevation: 8.0,
          );
        }
      
        void _storePosition(TapDownDetails details) {
          _tapPosition = details.globalPosition;
        }
      }
      

      然后这里是完整的代码(你必须调整一些东西,比如图像,并填写设备列表):

      import 'package:flutter/material.dart';
      import 'package:auto_size_text/auto_size_text.dart';
      import 'dart:core';
      
      class RecentsPage extends StatefulWidget {
        RecentsPage({Key key, this.title}) : super(key: key);
      
        final String title;
      
        @override
        _RecentsPageState createState() => _RecentsPageState();
      }
      
      class _RecentsPageState extends State<RecentsPage> {
      
        List<String> _recents;
      
        var _tapPosition;
      
        @override
        void initState() {
          super.initState();
          _tapPosition = Offset(0.0, 0.0);
          getRecents().then((value) {
            setState(() {
              _recents = value;
            });
          });
        }
      
        @override
        Widget build(BuildContext context) {
          return Scaffold(
            backgroundColor: Color(0xFFFFFFFF),
            body: SafeArea(
              child: Center(
                child: Column(
                  mainAxisAlignment: MainAxisAlignment.start,
                  crossAxisAlignment: CrossAxisAlignment.stretch,
                  children: <Widget>[
                    Container(height: 25),
                    Stack(
                      children: <Widget>[
                        Container(
                          padding: EdgeInsets.only(left: 40),
                          child: Center(
                            child: AutoSizeText(
                              "Recents",
                              maxLines: 1,
                              textAlign: TextAlign.center,
                              style: TextStyle(fontSize: 32),
                            ),
                          ),
                        ),
                        Container(
                          padding: EdgeInsets.only(left: 30, top: 0),
                          child: GestureDetector(
                              onTap: () => Navigator.of(context).pop(),
                              child: Transform.scale(
                                scale: 2.0,
                                child: Icon(
                                  Icons.chevron_left,
                                ),
                              )),
                        ),
                      ],
                    ),
                    Container(
                      height: 15,
                    ),
                    Container(
                      height: 2,
                      color: Colors.blue,
                    ),
                    Container(
                      height: 10,
                    ),
                    Flexible(
                      child: ListView(
                        padding: EdgeInsets.all(15.0),
                        children: ListTile.divideTiles(
                          context: context,
                          tiles: _getRecentTiles(),
                        ).toList(),
                      ),
                    ),
                    Container(height: 15),
                  ],
                ),
              ),
            ),
          );
        }
      
        List<Widget> _getRecentTiles() {
          List<Widget> devices = List<Widget>();
          String _dev;
          String _owner = "John Doe";
      
          if (_recents != null) {
            for (_dev in _recents.reversed) {
              if (_dev != null) {
                _dev = _dev.toUpperCase().trim();
      
                  String serial = "12341234";
      
                  devices.add(GestureDetector(
                      onTapDown: _storePosition,
                      onLongPress: () {
                        print("long press of $serial");
                        _showPopupMenu();
                      },
                      child: ListTile(
                        contentPadding: EdgeInsets.symmetric(vertical: 20),
                        leading: Transform.scale(
                            scale: 0.8,
                            child: Image(
                              image: _myImage,
                            )),
                        title: AutoSizeText(
                          "$_owner",
                          maxLines: 1,
                          style: TextStyle(fontSize: 22),
                        ),
                        subtitle: Text("Serial #: $serial"),
                        trailing: Icon(Icons.keyboard_arrow_right),
                      )));
              }
            }
          } else {
            devices.add(ListTile(
              contentPadding: EdgeInsets.symmetric(vertical: 20),
              title: AutoSizeText(
                "No Recent Devices",
                maxLines: 1,
                style: TextStyle(fontSize: 20),
              ),
              subtitle:
                  Text("Click the button to add a device"),
              onTap: () {
                print('add device');
              },
            ));
          }
          return devices;
        }
      
        _showPopupMenu() async {
          final RenderBox overlay = Overlay.of(context).context.findRenderObject();
      
          await showMenu(
            context: context,
            position: RelativeRect.fromRect(
                _tapPosition & Size(40, 40), // smaller rect, the touch area
                Offset.zero & overlay.size // Bigger rect, the entire screen
                ),
            items: [
              PopupMenuItem(
                child: Text("Show Usage"),
              ),
              PopupMenuItem(
                child: Text("Delete"),
              ),
            ],
            elevation: 8.0,
          );
        }
      
        void _storePosition(TapDownDetails details) {
          _tapPosition = details.globalPosition;
        }
      }
      

      【讨论】:

      • 我得到了空错误,因为 _tapPosition 甚至在菜单显示之前也没有被初始化。只需将其设置为 _tapPosition = Offset(0.0, 0.0);在构建函数之外,它不会出错,并且会在调用 onTapDown 时更新到新位置。
      • 对不起,我从来没有得到那个,但是是的,你可以将它写入 initState() 中的 Offset(0.0, 0.0) ,例如像这样的有状态小部件。感谢您的评论!
      • 如何设置_tapPosition = Offset(0.0, 0.0);我想根据不同手机的屏幕尺寸(宽度)将菜单位置设置在屏幕的右上角?请建议。谢谢
      • @Kamlesh 首先,在这个有状态的小部件开始时将 _tapPosition 设置为 0,0 是将其放入 initState() 函数中。我更新了上面的答案以包括那一行。但听起来你想要一些不同的东西,比如总是在任何尺寸屏幕的右上角显示弹出窗口?在这种情况下,其他帖子可能会为您提供更多帮助:stackoverflow.com/questions/61756271/…
      • offset: const Offset(0, -380),帮助了我。谢谢
      【解决方案3】:

      这是一个可重复使用的小部件,可以满足您的需要。只需用这个CopyableWidget 包裹你的Text 或其他Widget 并传入onGetCopyTextRequested。长按小部件时将显示复制菜单,将返回的文本内容复制到剪贴板,并在完成时显示Snackbar

      /// The text to copy to the clipboard should be returned or null if nothing can be copied
      typedef GetCopyTextCallback = String Function();
      
      class CopyableWidget extends StatefulWidget {
      
          final Widget child;
          final GetCopyTextCallback onGetCopyTextRequested;
      
          const CopyableWidget({
              Key key,
              @required this.child,
              @required this.onGetCopyTextRequested,
          }) : super(key: key);
      
          @override
          _CopyableWidgetState createState() => _CopyableWidgetState();
      }
      
      class _CopyableWidgetState extends State<CopyableWidget> {
      
          Offset _longPressStartPos;
      
          @override
          Widget build(BuildContext context) {
              return InkWell(
                  highlightColor: Colors.transparent,
                  onTapDown: _onTapDown,
                  onLongPress: () => _onLongPress(context),
                  child: widget.child
              );
          }
      
          void _onTapDown(TapDownDetails details) {
              setState(() {
                  _longPressStartPos = details?.globalPosition;
              });
          }
      
          void _onLongPress(BuildContext context) async {
              if (_longPressStartPos == null)
                  return;
      
              var isCopyPressed = await showCopyMenu(
                  context: context,
                  pressedPosition: _longPressStartPos
              );
              if (isCopyPressed == true && widget.onGetCopyTextRequested != null) {
                  var copyText = widget.onGetCopyTextRequested();
                  if (copyText != null) {
                      await Clipboard.setData(ClipboardData(text: copyText));
      
                      _showSuccessSnackbar(
                          context: context,
                          text: "Copied to the clipboard"
                      );
                  }
              }
          }
      
          void _showSuccessSnackbar({
              @required BuildContext context,
              @required String text
          }) {
              var scaffold = Scaffold.of(context, nullOk: true);
              if (scaffold != null) {
                  scaffold.showSnackBar(
                      SnackBar(
                          content: Row(
                              children: <Widget>[
                                  Icon(
                                      Icons.check_circle_outline,
                                      size: 24,
                                  ),
                                  SizedBox(width: 8),
                                  Expanded(
                                      child: Text(text)
                                  )
                              ],
                          )
                      )
                  );
              }
          }
      }
      
      Future<bool> showCopyMenu({
          BuildContext context,
          Offset pressedPosition
      }) {
          var x = pressedPosition.dx;
          var y = pressedPosition.dy;
      
          return showMenu<bool>(
              context: context,
              position: RelativeRect.fromLTRB(x, y, x + 1, y + 1),
              items: [
                  PopupMenuItem<bool>(value: true, child: Text("Copy")),
              ]
          );
      }
      

      【讨论】:

        猜你喜欢
        • 2010-12-22
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2022-12-01
        • 1970-01-01
        相关资源
        最近更新 更多