mrs.bassim
mrs.bassim

Reputation: 479

how to create a custom popup menu with flutter

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?

Upvotes: 16

Views: 29134

Answers (5)

Krishna
Krishna

Reputation: 1634

  This is custom Tooltipwidget. You can apply this widget to shape attribute. For example PopupmenuButton(shape: TooltipShapeWidget());

  class TooltipShapeWidget extends ShapeBorder {
  const TooltipShapeWidget();

  final BorderSide _side = BorderSide.none;
  final BorderRadiusGeometry _borderRadius = BorderRadius.zero;

  @override
  EdgeInsetsGeometry get dimensions => EdgeInsets.all(_side.width);

  @override
  Path getInnerPath(
    Rect rect, {
    TextDirection? textDirection,
  }) {
    final path = Path();

    path.addRRect(
  
_borderRadius.resolve(textDirection).toRRect(rect).deflate(_side.width),
    );

    return path;
  }

  @override
  Path getOuterPath(Rect rect, {TextDirection? textDirection}) {
    final path = Path();
    final rrect = _borderRadius.resolve(textDirection).toRRect(rect);

    path.moveTo(0, 10);
    path.quadraticBezierTo(0, 0, 10, 0);
    path.lineTo(rrect.width - 30, 0);
    path.lineTo(rrect.width - 20, -10);
    path.lineTo(rrect.width - 10, 0);
    path.quadraticBezierTo(rrect.width, 0, rrect.width, 10);
    path.lineTo(rrect.width, rrect.height - 10);
    path.quadraticBezierTo(
    rrect.width, rrect.height, rrect.width - 10, rrect.height);
    path.lineTo(10, rrect.height);
    path.quadraticBezierTo(0, rrect.height, 0, rrect.height - 10);

    return path;
  }

  @override
  void paint(Canvas canvas, Rect rect, {TextDirection? textDirection}) 
  {}

  @override
  ShapeBorder scale(double t) => RoundedRectangleBorder(
        side: _side.scale(t),
        borderRadius: _borderRadius * t,
      );
}

Upvotes: 0

                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,
                          ),
                        )
                      ],
                    ),
                  ),
                ),

Upvotes: 0

Elias Andualem
Elias Andualem

Reputation: 366

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.

Upvotes: 0

Shubham Gupta
Shubham Gupta

Reputation: 2007

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!

Upvotes: 6

Andrii Turkovskyi
Andrii Turkovskyi

Reputation: 29468

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

Upvotes: 19

Related Questions