how to create a custom popup menu with flutter

Viewed 19790

I want to create a popup menu when clicking on a button from the appbar .. i want something like this to appear:

enter image description here

is there a way to do this in flutter? a package or something?

4 Answers

I tried, but I've faced some problems with showing subwidget exactly this way. So, here two solutions:

class TestScreen extends StatefulWidget {
  @override
  State<StatefulWidget> createState() => _TestScreenState();
}

class _TestScreenState extends State<TestScreen> with SingleTickerProviderStateMixin {
  AnimationController animationController;
  bool _menuShown = false;

  @override
  void initState() {
    animationController = AnimationController(vsync: this, duration: Duration(milliseconds: 500));
    super.initState();
  }

  @override
  Widget build(BuildContext context) {    
    Animation opacityAnimation = Tween(begin: 0.0, end: 1.0).animate(animationController);
    if (_menuShown)
      animationController.forward();
    else
      animationController.reverse();
    return Scaffold(
      appBar: AppBar(
        actions: <Widget>[IconButton(icon: Icon(Icons.menu), onPressed: (){
          setState(() {
            _menuShown = !_menuShown;
          });
        })],
      ),
      body: Stack(
        overflow: Overflow.visible,
        children: <Widget>[
          Positioned(
            child: FadeTransition(
              opacity: opacityAnimation,
              child: _ShapedWidget(),
            ),
            right: 4.0,
            top: 16.0,
          ),
        ],
      ),
    );
  }
}

class _ShapedWidget extends StatelessWidget {
  _ShapedWidget();
  final double padding = 4.0;

  @override
  Widget build(BuildContext context) {
    return Center(
      child: Material(
          clipBehavior: Clip.antiAlias,
          shape:
          _ShapedWidgetBorder(borderRadius: BorderRadius.all(Radius.circular(padding)), padding: padding),
          elevation: 4.0,
          child: Container(
            padding: EdgeInsets.all(padding).copyWith(bottom: padding * 2),
            child: SizedBox(width: 150.0, height: 250.0, child: Center(child: Text('ShapedWidget'),),),
          )),
    );
  }
}

class _ShapedWidgetBorder extends RoundedRectangleBorder {
  _ShapedWidgetBorder({
    @required this.padding,
    side = BorderSide.none,
    borderRadius = BorderRadius.zero,
  }) : super(side: side, borderRadius: borderRadius);
  final double padding;

  @override
  Path getOuterPath(Rect rect, {TextDirection textDirection}) {
    return Path()
      ..moveTo(rect.width - 8.0 , rect.top)
      ..lineTo(rect.width - 20.0, rect.top - 16.0)
      ..lineTo(rect.width - 32.0, rect.top)
      ..addRRect(borderRadius
          .resolve(textDirection)
          .toRRect(Rect.fromLTWH(rect.left, rect.top, rect.width, rect.height - padding)));
  }
}

In this case subwidget is below appbar

class TestScreen extends StatefulWidget {
  @override
  State<StatefulWidget> createState() => _TestScreenState();
}

class _TestScreenState extends State<TestScreen> with SingleTickerProviderStateMixin {
  AnimationController animationController;
  bool _menuShown = false;

  @override
  void initState() {


    animationController = AnimationController(vsync: this, duration: Duration(milliseconds: 500));
    super.initState();
  }

  @override
  Widget build(BuildContext context) {

    Animation opacityAnimation = Tween(begin: 0.0, end: 1.0).animate(animationController);
    if (_menuShown)
      animationController.forward();
    else
      animationController.reverse();
    return Scaffold(
      appBar: AppBar(
        elevation: 0.0,
        actions: <Widget>[Stack(
          overflow: Overflow.visible,
          children: <Widget>[IconButton(icon: Icon(Icons.menu), onPressed: (){
          setState(() {
            _menuShown = !_menuShown;
          });    
        }),
          Positioned(
            child: FadeTransition(
              opacity: opacityAnimation,
              child: _ShapedWidget(onlyTop: true,),
            ),
            right: 4.0,
            top: 48.0,
          ),    
          ],)],
      ),
      body: Stack(
        overflow: Overflow.visible,
        children: <Widget>[
          Positioned(
            child: FadeTransition(
              opacity: opacityAnimation,
              child: _ShapedWidget(),
            ),
            right: 4.0,
            top: -4.0,
          ),
        ],
      ),
    );
  }
}  

class _ShapedWidget extends StatelessWidget {
  _ShapedWidget({this.onlyTop = false});
  final double padding = 4.0;
  final bool onlyTop;

  @override
  Widget build(BuildContext context) {
    return Center(
      child: Material(
          clipBehavior: Clip.antiAlias,
          shape:
          _ShapedWidgetBorder(borderRadius: BorderRadius.all(Radius.circular(padding)), padding: padding),
          elevation: 4.0,
          child: Container(
            padding: EdgeInsets.all(padding).copyWith(bottom: padding * 2),
            child: onlyTop ? SizedBox(width: 150.0, height: 20.0,) :  SizedBox(width: 150.0, height: 250.0, child: Center(child: Text('ShapedWidget'),),),
          )),
    );
  }
}

class _ShapedWidgetBorder extends RoundedRectangleBorder {
  _ShapedWidgetBorder({
    @required this.padding,
    side = BorderSide.none,
    borderRadius = BorderRadius.zero,
  }) : super(side: side, borderRadius: borderRadius);
  final double padding;

  @override
  Path getOuterPath(Rect rect, {TextDirection textDirection}) {
    return Path()
      ..moveTo(rect.width - 8.0 , rect.top)
      ..lineTo(rect.width - 20.0, rect.top - 16.0)
      ..lineTo(rect.width - 32.0, rect.top)
      ..addRRect(borderRadius
          .resolve(textDirection)
          .toRRect(Rect.fromLTWH(rect.left, rect.top, rect.width, rect.height - padding)));
  }
}

In this case top of subwidget is on appbar, but appbar has to have 0.0 elevation

Actually, both of these solutions are not complete in my opinion, but it can help you to find what you need

It might be too late for an answer. But this can be simply achieved by using OverlayEntry widget. We create a widget of that shape and pass it to OverlayEntry widget and then use Overlay.of(context).insert(overlayEntry) to show the overlay and overlayEntry.remove method to remove it.

Here is a medium link to create a Custom DropDown Menu

Hope this helps!

There is a package called flutter_portal which works like Overlay/OverlayEntry but in a declarative way. You can use it for implementing custom tooltips, context menus, or dialogs.

                CustomPopupMenu(
                  pressType: PressType.singleClick,
                  controller: menu,
                  arrowColor: AppColor.white,
                  menuBuilder: () => ClipRect(
                      clipBehavior: Clip.hardEdge,
                      child: Container(
                        height: MediaQuery.of(context).size.height *
                            ComponentSize.container1height,
                        width: MediaQuery.of(context).size.width *
                            ComponentSize.conatiner1width,
                        decoration: BoxDecoration(
                          borderRadius: BorderRadius.circular(
                              ComponentSize.borderradius),
                          color: AppColor.white,
                        ),
                        child: ListView.builder(
                          itemCount: Details.length,
                          itemBuilder: (context, index) {
                            return Column(
                              children: [
                                InkWell(
                                  onTap: () {
                                   
                                   do somthing

                                  },
                                  child: Column(
                                    children: [
                                      Container(
                                        padding: EdgeInsets.only(
                                            left:
                                                ComponentSize.paddingleft),
                                        alignment: Alignment.centerLeft,
                                        child: Text(
                                          Details[index],
                                          style: const TextStyle(
                                              color: Colors.black,
                                              fontFamily: 'Taml_001'),
                                          textAlign: TextAlign.start,
                                        ),
                                      ),
                                      Container(
                                        alignment: Alignment.centerLeft,
                                        padding: EdgeInsets.only(
                                            left:
                                                ComponentSize.paddingleft),
                                        child: Text(Details[index],
                                            style: TextStyle(
                                                color: AppColor.black
                                                    .withOpacity(
                                                        ComponentSize
                                                            .opacity1),
                                                fontSize: ComponentSize
                                                    .containerfontsize)),
                                      )
                                    ],
                                  ),
                                ),
                                const Divider(),
                              ],
                            );
                          },
                        ),
                      )),
                  child: Container(
                    color: AppColor.white,
                    padding: EdgeInsets.only(
                        top: ComponentSize.paddingbottom,
                        bottom: ComponentSize.paddingtop,
                        left: ComponentSize.padding1left),
                    width: ComponentSize.container2width,
                    height: ComponentSize.container2height,
                    child: Row(
                      mainAxisAlignment: MainAxisAlignment.spaceBetween,
                      children: [
                        SizedBox(
                          child: Column(
                            mainAxisAlignment:
                                MainAxisAlignment.spaceBetween,
                            crossAxisAlignment: CrossAxisAlignment.start,
                            children: [
                              SizedBox(
                                width: ComponentSize.textcontainerwidth,
                                height: ComponentSize.textcontainerheight,
                                child: SingleChildScrollView(
                                  scrollDirection: Axis.horizontal,
                                  child: Text(
                                    Tamil,
                                    style: const TextStyle(
                                        color: Colors.black,
                                        fontFamily: 'Taml_001'),
                                  ),
                                ),
                              ),
                              SizedBox(
                                width: ComponentSize.textcontainerwidth,
                                height: ComponentSize.textcontainerheight,
                                child: SingleChildScrollView(
                                  scrollDirection: Axis.horizontal,
                                  child: Text(
                                    English,
                                    style: const TextStyle(
                                        color: Colors.black),
                                  ),
                                ),
                              )
                            ],
                          ),
                        ),
                        SizedBox(
                          child: Icon(
                            Icons.expand_more,
                            size: ComponentSize.iconarrowsize,
                            color: Colors.black,
                          ),
                        )
                      ],
                    ),
                  ),
                ),
Related