如何在颤振中按/手指/鼠标/光标位置显示菜单

时间:2019-01-20 19:47:15

标签: dart flutter flutter-layout

我有这段来自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"),
                ),
              ],
            ),
          ),
        ],
      );

这段代码很好用,除了所创建的弹出窗口在固定位置外,我如何制作它以便使其在鼠标/按下/手指/光标位置或附近的某个位置弹出,就像当您要复制并粘贴到手机上。 (此对话框弹出窗口将不会用于复制和粘贴)

2 个答案:

答案 0 :(得分:0)

这是一个可重复使用的小部件,它可以满足您的需求。只需用此Text包装您的Widget或其他CopyableWidget并传递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")),
        ]
    );
}

答案 1 :(得分:0)

我可以使用以下答案解决类似的问题: 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();
    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;
  }
}