Reputation: 3801
How do I open a popup menu from a second widget?
final button = new PopupMenuButton(
itemBuilder: (_) => <PopupMenuItem<String>>[
new PopupMenuItem<String>(
child: const Text('Doge'), value: 'Doge'),
new PopupMenuItem<String>(
child: const Text('Lion'), value: 'Lion'),
],
onSelected: _doSomething);
final tile = new ListTile(title: new Text('Doge or lion?'), trailing: button);
I want to open the button
's menu by tapping on tile
.
Upvotes: 47
Views: 77065
Reputation: 3228
if you are using Material showMenu
but you menu doesn't work properly or opens in wrong place follow my answer.
this answer is based on answer of Vishal Singh.
in GestureDetector
use onLongPressStart
or onTapUp
for sending offset to function.
onLongPressStart: (detail){
_showPopupMenu(detail.globalPosition);
},
onLongPress is equivalent to (and is called immediately after) onLongPressStart.
onTapUp, which is called at the same time (with onTap) but includes details regarding the pointer position.
and for menu position do some thing like below
position: RelativeRect.fromDirectional(textDirection: Directionality.of(context), start: left, top: top, end: left+2, bottom: top+2)
full code
_showPopupMenu(Offset offset) async {
double left = offset.dx;
double top = offset.dy;
await showMenu(
context: context,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(
Radius.circular(AppConst.borderRadiusSmall))),
position: RelativeRect.fromDirectional(textDirection: Directionality.of(context), start: left, top: top, end: left+2, bottom: top+2),
items: _getMenuItems(menu),
elevation: 8.0,
).then((value) {
value?.onTap.call();
});
}
Upvotes: 0
Reputation: 3522
This works, but is inelegant (and has the same display problem as Rainer's solution above:
class _MyHomePageState extends State<MyHomePage> {
final GlobalKey _menuKey = GlobalKey();
@override
Widget build(BuildContext context) {
final button = PopupMenuButton(
key: _menuKey,
itemBuilder: (_) => const<PopupMenuItem<String>>[
PopupMenuItem<String>(
child: Text('Doge'), value: 'Doge'),
PopupMenuItem<String>(
child: Text('Lion'), value: 'Lion'),
],
onSelected: (_) {});
final tile =
ListTile(title: Text('Doge or lion?'), trailing: button, onTap: () {
// This is a hack because _PopupMenuButtonState is private.
dynamic state = _menuKey.currentState;
state.showButtonMenu();
});
return Scaffold(
body: Center(
child: tile,
),
);
}
}
I suspect what you're actually asking for is something like what is tracked by https://github.com/flutter/flutter/issues/254 or https://github.com/flutter/flutter/issues/8277 -- the ability to associated a label with a control and have the label be clickable -- and is a missing feature from the Flutter framework.
Upvotes: 49
Reputation: 268314
Screenshot:
Full code:
class MyPage extends StatelessWidget {
final GlobalKey<PopupMenuButtonState<int>> _key = GlobalKey();
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
actions: [
PopupMenuButton<int>(
key: _key,
itemBuilder: (context) {
return <PopupMenuEntry<int>>[
PopupMenuItem(child: Text('0'), value: 0),
PopupMenuItem(child: Text('1'), value: 1),
];
},
),
],
),
body: RaisedButton(
onPressed: () => _key.currentState.showButtonMenu(),
child: Text('Open/Close menu'),
),
);
}
}
Upvotes: 11
Reputation: 1511
I think it would be better do it in this way, rather than showing a PopupMenuButton
void _showPopupMenu() async {
await showMenu(
context: context,
position: RelativeRect.fromLTRB(100, 100, 100, 100),
items: [
PopupMenuItem<String>(
child: const Text('Doge'), value: 'Doge'),
PopupMenuItem<String>(
child: const Text('Lion'), value: 'Lion'),
],
elevation: 8.0,
);
}
There will be times when you would want to display _showPopupMenu at the location where you pressed on the button Use GestureDetector for that
final tile = new ListTile(
title: new Text('Doge or lion?'),
trailing: 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,
);
}
Upvotes: 59
Reputation: 7978
I found a solution to your question. You can provide a child to PopupMenuButton which can be any Widget including a ListTile (see code below). Only problem is that the PopupMenu opens on the left side of the ListTile.
final popupMenu = new PopupMenuButton(
child: new ListTile(
title: new Text('Doge or lion?'),
trailing: const Icon(Icons.more_vert),
),
itemBuilder: (_) => <PopupMenuItem<String>>[
new PopupMenuItem<String>(
child: new Text('Doge'), value: 'Doge'),
new PopupMenuItem<String>(
child: new Text('Lion'), value: 'Lion'),
],
onSelected: _doSomething,
)
Upvotes: 7
Reputation: 7978
I don't think there is a way to achieve this behaviour. Although you can attach an onTap
attribute to the tile, you can't access the MenuButton from the 'outside'
An approach you could take is to use ExpansionPanels because they look like ListTiles and are intended to allow easy modification and editing.
Upvotes: 0