Search code examples
dartflutterflutter-layout

Flutter - showing a PopupMenuButton in BottomNavigationBar


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.


Solution

  • 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()));
      }