如何在颤动的按下/手指/鼠标/光标位置显示菜单
Posted
技术标签:
【中文标题】如何在颤动的按下/手指/鼠标/光标位置显示菜单【英文标题】: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"),
),
],
),
),
],
);
此代码效果很好,除了创建的弹出窗口位于固定位置之外,我将如何使其弹出在鼠标/按下/手指/光标位置或附近的某个位置,有点像当您想在手机上复制和粘贴。 (此对话框弹出不会用于复制和粘贴)
【问题讨论】:
【参考方案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.***_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://***.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() 函数中。我更新了上面的答案以包括那一行。但听起来你想要一些不同的东西,比如总是在任何尺寸屏幕的右上角显示弹出窗口?在这种情况下,其他帖子可能会为您提供更多帮助:***.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")),
]
);
【讨论】:
以上是关于如何在颤动的按下/手指/鼠标/光标位置显示菜单的主要内容,如果未能解决你的问题,请参考以下文章