Flutter实现类似Android中的PopupWindow控件

最近在网上看到一段话。产品有三宝-弹窗,浮层加引导,设计有三宝-透明,阴影加圆角,运营有三宝-短信,push加红包。在日常开发中经常会遇到弹窗、浮层之类的效果,这些在Android中实现很简单,可以用PopupWindow完成。但是在flutter中怎么做呢,起初我是用flutter中的PopupMenuItem来做,但是这种效果体验特差,不能自定义内容、不能自定义位置,所以放弃了该方案。于是我看了下PopupMenuItem中的源码,发现PopupRoute可以自定义弹窗。

效果如下:

在这里插入图片描述

自定义PopupRoute

代码如下:

class PopRoute extends PopupRoute {
  final Duration _duration = Duration(milliseconds: 200);
  Widget child;

  PopRoute({@required this.child});

  ///弹出蒙层的颜色,null则为透明
  @override
  Color get barrierColor => null;

  @override
  bool get barrierDismissible => true;

  @override
  String get barrierLabel => null;

  @override
  Widget buildPage(BuildContext context, Animation<double> animation,
      Animation<double> secondaryAnimation) {
    return child;
  }

  @override
  Duration get transitionDuration => _duration;
}

根据targetwidget定位

我们的需求是点击一个按钮在这个按钮上下左右弹出浮层,那么就必须targetwidget在屏幕中的位置坐标,获取位置可以用flutter中的RenderObject

 RenderBox renderBox = widget.popKey.currentContext.findRenderObject();
 Offset localToGlobal = renderBox.localToGlobal(Offset.zero); //targetWidget的坐标位置
 Size size = renderBox.size; //targetWidget的size

计算popupwindow偏移量

根据popupwindow的宽高和targetwidget计算出popupwindow的偏移量

 RenderBox buttonBox = buttonKey.currentContext.findRenderObject();
      var buttonSize = buttonBox.size; //button的size
      switch (widget.popDirection) {
        case PopDirection.left:
          left = localToGlobal.dx - buttonSize.width - widget.offset;
          top = localToGlobal.dy + size.height / 2 - buttonSize.height / 2;
          break;
        case PopDirection.top:
          left = localToGlobal.dx + size.width / 2 - buttonSize.width / 2;
          top = localToGlobal.dy - buttonSize.height - widget.offset;
          fixPosition(buttonSize);
          break;
        case PopDirection.right:
          left = localToGlobal.dx + size.width + widget.offset;
          top = localToGlobal.dy + size.height / 2 - buttonSize.height / 2;
          break;
        case PopDirection.bottom:
          left = localToGlobal.dx + size.width / 2 - buttonSize.width / 2;
          top = localToGlobal.dy + size.height + widget.offset;
          fixPosition(buttonSize);
          break;
      }

popupwindow整体布局

@override
  Widget build(BuildContext context) {
    return Material(
      color: Colors.transparent,
      child: GestureDetector(
        child: Stack(
          children: <Widget>[
            Container(
              width: MediaQuery.of(context).size.width,
              height: MediaQuery.of(context).size.height,
              color: Colors.transparent,
            ),
            Positioned(
              child: GestureDetector(
                child: widget.popWidget == null
                    ? _buildWidget(widget.msg)
                    : _buildCustomWidget(widget.popWidget),
              ),
              //根据`popupwindow`的宽高和`targetwidget`计算出popupwindow的偏移量
              left: left,x
              top: top,
            )
          ],
        ),
        onTap: () {
          Navigator.pop(context);
        },
      ),
    );
  }

使用方法

定义了一个showPopWindow的静态方法,在需要弹出popupWindow时,调用这个方法传入popWidget等参数即可。

static void showPopWindow(context, String msg, GlobalKey popKey,
      [PopDirection popDirection = PopDirection.bottom,
      Widget popWidget,
      double offset = 0]) {
    Navigator.push(
        context,
        PopRoute(
            child: PopupWindow(
          msg: msg,
          popKey: popKey,
          popDirection: popDirection,
          popWidget: popWidget,
          offset: offset,
        )));
  }

完整代码

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值