DogeLion
DogeLion

Reputation: 3801

How to open a PopupMenuButton?

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

Answers (6)

Sajjad
Sajjad

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

Eric Seidel
Eric Seidel

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

CopsOnRoad
CopsOnRoad

Reputation: 268314

Screenshot:

enter image description here


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

Vishal Singh
Vishal Singh

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

Rainer Wittmann
Rainer Wittmann

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

Rainer Wittmann
Rainer Wittmann

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

Related Questions