Flutter PopupMenuButton onLongPressed
Я пытаюсь показать контекст меню в пользовательском виджете, который я создал, когда он долго нажимается (при касании происходит другое поведение).
Я пытался использовать GestureDetector с onLongPress и использовать функцию showMenu, но она показывает меню в углу, а не поверх нажатого виджета. Я видел обходной путь, чтобы получить положение виджета и передать его в showMenu, но мне это кажется беспорядочным.
return new GestureDetector(
child: _defaultBuild(),
onTap: onTap,
onLongPress: () {
showMenu(
items: <PopupMenuEntry>[
PopupMenuItem(
//value: this._index,
child: Row(
children: <Widget>[
Text("Context item1")
],
),
)
],
context: context,
position: _getPosition(context)
);
}
);
RelativeRect _getPosition(BuildContext context) {
final RenderBox bar = context.findRenderObject();
final RenderBox overlay = Overlay.of(context).context.findRenderObject();
final RelativeRect position = RelativeRect.fromRect(
Rect.fromPoints(
bar.localToGlobal(bar.size.bottomRight(Offset.zero), ancestor: overlay),
bar.localToGlobal(bar.size.bottomRight(Offset.zero), ancestor: overlay),
),
Offset.zero & overlay.size,
);
return position;
}
Я также пытался использовать PopupMenuButton, но я не смог показать меню на LongPressed.
Есть идеи?
2 ответа
showMenu()
хорошо работает на моем конце. Похоже, проблема связана с тем, как меню позиционируется с помощью RelativeRect. Вместо
RelativeRect.fromRect()
, Я использовал
RelativeRect.fromSize()
на моем.
RelativeRect _getRelativeRect(GlobalKey key){
return RelativeRect.fromSize(
_getWidgetGlobalRect(key), const Size(200, 200));
}
Rect _getWidgetGlobalRect(GlobalKey key) {
final RenderBox renderBox =
key.currentContext!.findRenderObject() as RenderBox;
var offset = renderBox.localToGlobal(Offset.zero);
debugPrint('Widget position: ${offset.dx} ${offset.dy}');
return Rect.fromLTWH(offset.dx / 3.1, offset.dy * 1.05,
renderBox.size.width, renderBox.size.height);
}
Вот полный образец, который вы можете попробовать.
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key, required this.title}) : super(key: key);
final String title;
@override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
final widgetKey = GlobalKey();
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: GestureDetector(
key: widgetKey,
child: Container(
height: 60,
width: 120,
color: Colors.lightBlueAccent,
child: const Center(child: Text('Show Menu')),
),
onLongPress: () {
showMenu(
items: <PopupMenuEntry>[
PopupMenuItem(
//value: this._index,
child: Row(
children: const [Text("Context item 1")],
),
)
],
context: context,
position: _getRelativeRect(widgetKey),
);
},
),
),
);
}
RelativeRect _getRelativeRect(GlobalKey key){
return RelativeRect.fromSize(
_getWidgetGlobalRect(key), const Size(200, 200));
}
Rect _getWidgetGlobalRect(GlobalKey key) {
final RenderBox renderBox =
key.currentContext!.findRenderObject() as RenderBox;
var offset = renderBox.localToGlobal(Offset.zero);
debugPrint('Widget position: ${offset.dx} ${offset.dy}');
return Rect.fromLTWH(offset.dx / 3.1, offset.dy * 1.05,
renderBox.size.width, renderBox.size.height);
}
}
Следующее решение немного чище.
Использование устраняет необходимость сохранять позицию в атрибуте всего виджета. Это обычный подход.
Причина
GestureDetector.onLongPressStart(offset)
вызывается прямо передGestureDetector.onLongPress()
что, таким образом, не нужно.
Базовая структура виджета
GestureDetector
.onLongPressStart(position) => showMenu(position)
.onTap() { /* standard tap on ListTile */ }
.child: ListTile(title: Text('Title'))
Полный пример
import 'package:flutter/material.dart';
class ListTileWithPopup extends StatelessWidget {
const ListTileWithPopup({super.key});
@override
Widget build(BuildContext context) {
return GestureDetector(
onLongPressStart: (value) =>
_showCustomMenu(context, value.globalPosition),
onTap: () => {
// tap on tile: do something
},
child: const ListTile(title: Text('list tile with popup menu')));
}
void _showCustomMenu(BuildContext context, Offset offset) {
final RenderObject overlay =
Overlay.of(context).context.findRenderObject()!;
showMenu(
context: context,
items: [
PopupMenuItem(
child: const Row(children: [
Icon(Icons.edit),
SizedBox(width: 16),
Text('Edit')
]),
onTap: () {
// tap on menu entry: do something
},
),
],
position: RelativeRect.fromRect(
Rect.fromLTWH(offset.dx, offset.dy, 30, 30),
Rect.fromLTWH(0, 0, overlay.paintBounds.size.width,
overlay.paintBounds.size.height)));
}
}