6

I am trying to show a menu when a navigation bar item is clicked. This was my attempt:

  @override
  Widget build(BuildContext context) {
    return DefaultTabController(
        length: 3,
        child: Scaffold(
            appBar: MyAppBar(
              title: "Home",
              context: context,
            ),
            bottomNavigationBar: BottomNavigationBar(
              items: [
                BottomNavigationBarItem(
                    icon: new Icon(Icons.home), title: Text('Home')),
                BottomNavigationBarItem(
                    icon: new Icon(Icons.book), title: Text('Second')),
                BottomNavigationBarItem(
                    icon: new PopupMenuButton(
                      icon: Icon(Icons.add),
                      itemBuilder: (_) => <PopupMenuItem<String>>[
                            new PopupMenuItem<String>(
                                child: const Text('test1'), value: 'test1'),
                            new PopupMenuItem<String>(
                                child: const Text('test2'), value: 'test2'),
                          ],
                    ),
                    title: Text('more')),
              ],
              currentIndex: 0,
            ),
            body: new Container()));
  }

I encountered two problems. First one is the display of the NavigationBarItem. There is a padding between the icon the title that I could not remove (even by adding padding: EdgeInsets.all(0.0)) (as the picture below shows). And the second is that I need to click exactly on the icon for the menu to appear. enter image description here enter image description here

I tried calling showMenu directly (the method that PopupMenuButton calls) when a BottomNavigationBarItem of index=2 (for example) is clicked. But it was tricky how to determine the location of origin where the menu should appear from.

M20
  • 1,032
  • 2
  • 15
  • 34

2 Answers2

16

Here's an attempt that uses the showMenu directly and calling the function buttonMenuPosition to get the position for the menu. It's fairly fragile, but you can change the location of the button to the middle for example with bar.size.center instead of bar.size.bottomRight. With some MATH and by manually manipulating Offset objects (if/when you have more than 3 items), you can change the location to have the menu on one that isn't the center or at the end).

RelativeRect buttonMenuPosition(BuildContext c) {
    final RenderBox bar = c.findRenderObject();
    final RenderBox overlay = Overlay.of(c).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;
  }


  @override
  Widget build(BuildContext context) {

    final key = GlobalKey<State<BottomNavigationBar>>();

    return DefaultTabController(
        length: 3,
        child: Scaffold(
            appBar: AppBar(
              title: Text("Home"),
            ),
            bottomNavigationBar: BottomNavigationBar(
              key: key,
              items: [
                const BottomNavigationBarItem(
                    icon: Icon(Icons.home), title: Text('Home')),
                const BottomNavigationBarItem(
                    icon: Icon(Icons.book), title: Text('Second')),
                const BottomNavigationBarItem(
                    icon: Icon(Icons.add), title: Text('more')),
              ],
              currentIndex: 0,
              onTap: (index) async {
                final position = buttonMenuPosition(key.currentContext);
                if (index == 2) {
                  final result = await showMenu(
                    context: context,
                    position: position,
                    items: <PopupMenuItem<String>>[
                      const PopupMenuItem<String>(
                          child: Text('test1'), value: 'test1'),
                      const PopupMenuItem<String>(
                          child: Text('test2'), value: 'test2'),
                    ],
                  );
                }
              },
            ),
            body: Container()));
  }
Ringil
  • 6,277
  • 2
  • 23
  • 37
  • This answer deserves more upvotes!! how to position the showMenu widget is not at all clear in the official docs, especially when you have to position it relative to another widget. – Vishnu Nair Nov 15 '19 at 10:45
  • I've similar question, will you answer that? https://stackoverflow.com/questions/75512542/showmenu-anchor-from-bottom – abdullah_bd Feb 20 '23 at 19:19
5

Here's my attempt at it:

@override
  Widget build(BuildContext context) {
    return Material(
        child:  DefaultTabController(
        length: 3,
        child: Scaffold(
            appBar: AppBar(
              title: Text("Home"),
            ),
            bottomNavigationBar: BottomNavigationBar(
              items: [
                BottomNavigationBarItem(
                    icon: new Icon(Icons.home), title: Text('Home')),
                BottomNavigationBarItem(
                    icon: new Icon(Icons.book), title: Text('Second')),
                BottomNavigationBarItem(
                    icon: new Icon(Icons.add),
                    title: Text('More')),
              ],
              currentIndex: 0,
              onTap: (int index) async {
                if(index == 2){
                  await showMenu<String>(
                    context: context,
                    position: RelativeRect.fromLTRB(1000.0, 1000.0, 0.0, 0.0),
                    items: <PopupMenuItem<String>>[
                      new PopupMenuItem<String>(
                                child: const Text('test1'), value: 'test1'),
                            new PopupMenuItem<String>(
                                child: const Text('test2'), value: 'test2'),
                    ],
                    elevation: 8.0,
                  );
                }
              },
            ),
            body: new Container())));
  }

Basically using the showMenu method as you said except I've put the values for the RelativeRect as 1000.0 so that it'll be in the bottom right of any device. You could mess around with these values to get a position more right above the icon but I think having it like this works well:

enter image description here

soupjake
  • 3,293
  • 3
  • 17
  • 32
  • this is hardcoding of the rect value and is not "scalable" for other devices, is it? – denis631 Apr 10 '19 at 11:54
  • I would prefer using `Size _size = MediaQuery.of(context).size;` and then set the width to `_size.width` and height to `_size.height` on the left and top values. – Nixon Kosgei Sep 13 '19 at 06:54