如何使用颤振创建自定义弹出菜单

Posted

技术标签:

【中文标题】如何使用颤振创建自定义弹出菜单【英文标题】:how to create a custom popup menu with flutter 【发布时间】:2019-05-17 13:51:34 【问题描述】:

我想在单击应用栏上的按钮时创建一个弹出菜单。我希望出现这样的内容:

有没有办法在颤振中做到这一点?包什么的?

【问题讨论】:

解决方案之一是创建您自己的小部件并在点击菜单按钮时显示/隐藏它 @AndreyTurkovsky 如何创建一个形状为这种形状的小部件? @AndreyTurkovsky 如果可以的话,请给我一个例子? 【参考方案1】:

我试过了,但我在以这种方式显示子小部件时遇到了一些问题。所以,这里有两个解决方案:

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)));
  

在这种情况下,子小部件位于应用栏下方

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)));
  

在这种情况下,子小部件的顶部位于 appbar 上,但 appbar 的高度必须为 0.0

其实我觉得这两种解决方案都不完整,但是可以帮助你找到你需要的东西

【讨论】:

我想在第一个代码中询问一些事情......当我点击按钮时,弹出窗口立即显示......然后当我再次点击使其消失时......有一个滞后直到它消失...如何消除这种滞后? 我不知道。我能想到的唯一一件事 - 尝试构建发布。在调试中,动画有足够的麻烦 我注意到弹出容器会在屏幕出现与否时取代屏幕..我在它下面有一个列表视图..我无法在弹出边界中滚动..有什么办法解决这个问题? 尝试将FadeTransition改为ScaleTransition 我有一个小问题.. 我将菜单放在左侧而不是右侧.. 我只想将顶部的矩形也移动到菜单的左侧。 . 怎么办?我应该改变什么?【参考方案2】:

现在回答可能为时已晚。但这可以通过使用OverlayEntry 小部件来简单地实现。我们创建该形状的小部件并将其传递给OverlayEntry 小部件,然后使用Overlay.of(context).insert(overlayEntry) 显示覆盖并使用overlayEntry.remove 方法将其删除。

这是创建Custom DropDown Menu的中型链接

希望这会有所帮助!

【讨论】:

【参考方案3】:

有一个名为 flutter_portal 的包,它的工作方式类似于 Overlay/OverlayEntry,但以声明方式。您可以使用它来实现自定义工具提示、上下文菜单或对话框。

【讨论】:

以上是关于如何使用颤振创建自定义弹出菜单的主要内容,如果未能解决你的问题,请参考以下文章

创建一个弹出菜单来调用自定义导出器(Blender 2.6 API)?

Android自定义下拉/弹出菜单

创建自定义弹出对话框菜单

Android自定义下拉菜单/弹出菜单

Delphi自定义弹出/下拉菜单,如何?

在自定义视图上显示弹出菜单时不要关闭键盘