How do I open a popup menu from a second widget?
final button = new PopupMenuButton(
itemBuilder: (_) => >[
Use popup_menu package as a library
Add popup_menu: ^1.0.5
in your pubspec.yaml
dependencies. And import it:
import 'package:popup_menu/popup_menu.dart';
First, you should set the context at somewhere in you code. Like below:
PopupMenu.context = context;
Then create a showPopup
widget and pass the required params:
void showPopup(Offset offset) {
PopupMenu menu = PopupMenu(
// backgroundColor: Colors.teal,
// lineColor: Colors.tealAccent,
maxColumn: 3,
items: [
MenuItem(title: 'Copy', image: Image.asset('assets/copy.png')),
MenuItem(title: 'Mail', image: Icon(Icons.mail, color: Colors.white)),
MenuItem(title: 'Power',image: Icon(Icons.power, color: Colors.white,)),
],
onClickMenu: onClickMenu,
stateChanged: stateChanged,
onDismiss: onDismiss);
menu.show(rect: Rect.fromPoints(offset, offset));
}
void stateChanged(bool isShow) {
print('menu is ${isShow ? 'showing' : 'closed'}');
}
void onClickMenu(MenuItemProvider item) {
print('Click menu -> ${item.menuTitle}');
}
void onDismiss() {
print('Menu is dismiss');
}
Here is the open popup
@override
Widget build(BuildContext context) {
PopupMenu.context = context; // This is the set context
return Scaffold(
appBar: AppBar(
title: Text('Show Popup')),
body: Stack(
children: <Widget>[
ListView.builder(
itemCount: list.length,
itemBuilder: (context, index) {
return MaterialButton(
child: GestureDetector(
onTapUp: (TapUpDetails details) {
showPopup(details.globalPosition);
},
child: ListTile(
leading: IconButton(
icon: Icon(Icons.restaurant_menu),
),
title: Text("Select your categories"),
subtitle: Text("Sub Title"),
// onTap: showPopup,
),
),
);
},
),
],
),
);
}
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,
);
}
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,
)
This works, but is inelegant (and has the same display problem as Rainer's solution above:
class _MyHomePageState extends State<MyHomePage> {
final GlobalKey _menuKey = new GlobalKey();
@override
Widget build(BuildContext context) {
final button = new PopupMenuButton(
key: _menuKey,
itemBuilder: (_) => <PopupMenuItem<String>>[
new PopupMenuItem<String>(
child: const Text('Doge'), value: 'Doge'),
new PopupMenuItem<String>(
child: const Text('Lion'), value: 'Lion'),
],
onSelected: (_) {});
final tile =
new ListTile(title: new Text('Doge or lion?'), trailing: button, onTap: () {
// This is a hack because _PopupMenuButtonState is private.
dynamic state = _menuKey.currentState;
state.showButtonMenu();
});
return new Scaffold(
body: new 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.
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'),
),
);
}
}
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.