part of cool_ui; class CupertinoPopoverButton extends StatelessWidget{ final Widget child; final Widget popoverBody; final double popoverWidth; final double popoverHeight; final Color popoverColor; final double radius; final Duration transitionDuration; const CupertinoPopoverButton({ @required this.child, @required this.popoverBody, this.popoverColor=Colors.white, @required this.popoverWidth, @required this.popoverHeight, this.transitionDuration=const Duration(milliseconds: 200), this.radius=13.0}); @override Widget build(BuildContext context) { // TODO: implement build return GestureDetector( behavior: HitTestBehavior.translucent, onTap: (){ showGeneralDialog( context: context, pageBuilder: (BuildContext buildContext, Animation animation, Animation secondaryAnimation) { final ThemeData theme = Theme.of(context, shadowThemeOnly: true); var offset = WidgetUtils.getWidgetLocalToGlobal(context); var bounds = WidgetUtils.getWidgetBounds(context); final Widget pageChild = CupertinoPopover( transitionDuration: transitionDuration, attachRect:Rect.fromLTWH(offset.dx, offset.dy, bounds.width, bounds.height), child: popoverBody, width: popoverWidth, height: popoverHeight, color: popoverColor, context: context, radius: radius,); return Builder( builder: (BuildContext context) { return theme != null ? Theme(data: theme, child: pageChild) : pageChild; } ); }, barrierDismissible: true, barrierLabel: MaterialLocalizations.of(context).modalBarrierDismissLabel, barrierColor: Colors.black54, transitionDuration: transitionDuration, transitionBuilder: _buildMaterialDialogTransitions,); }, child: child, ); } Widget _buildMaterialDialogTransitions(BuildContext context, Animation animation, Animation secondaryAnimation, Widget child) { return FadeTransition( opacity: CurvedAnimation( parent: animation, curve: Curves.easeOut, ), child: child, ); } } class CupertinoPopover extends StatefulWidget { final Rect attachRect; final Widget child; final double width; final double height; final Color color; final double radius; final Duration transitionDuration; CupertinoPopover({ @required this.attachRect, @required this.child, @required this.width, @required this.height, this.color=Colors.white, this.transitionDuration = const Duration(milliseconds: 150), @required BuildContext context, this.radius=13.0}):super(){ ScreenUtil.getInstance().init(context); } @override CupertinoPopoverState createState() => new CupertinoPopoverState(); } class CupertinoPopoverState extends State with TickerProviderStateMixin{ static const double _arrowWidth = 26.0; static const double _arrowHeight = 13.0; double left; double top; Rect _arrowRect; Rect _bodyRect; Animation doubleAnimation; AnimationController animation; /// 是否箭头向上 bool isArrowUp; @override void initState() { // TODO: implement initState isArrowUp = ScreenUtil.screenHeight > widget.attachRect.bottom + widget.height + _arrowWidth; super.initState(); calcRect(); animation = new AnimationController( duration: widget.transitionDuration, vsync: this ); // Tween({T begin, T end }):创建tween(补间) doubleAnimation = new Tween(begin: 0.0, end: 1.0).animate(animation)..addListener((){ setState((){}); }); animation.forward(from: 0.0); } @override Widget build(BuildContext context) { var bodyMiddleX = _bodyRect.left + _bodyRect.width / 2; // 计算Body的X轴中间点 var arrowMiddleX = _arrowRect.left + _arrowRect.width /2; //计算箭头的X轴中间点 var leftOffset = (arrowMiddleX - bodyMiddleX) * (1 - doubleAnimation.value); //计算X轴缩小的偏移值 return Stack( children: [ Positioned( left:left + leftOffset, top:top, child:ScaleTransition( alignment: isArrowUp?Alignment.topCenter:Alignment.bottomCenter, scale: doubleAnimation, child: ClipPath( clipper:ArrowCliper( arrowRect:_arrowRect, bodyRect: _bodyRect, isArrowUp: isArrowUp, radius: widget.radius ), child: Container( padding: EdgeInsets.only(top:isArrowUp?_arrowHeight:0.0), color: Colors.white, width: widget.width, height: _bodyRect.height + _arrowHeight, child: Material(child: widget.child) ),), ), ) ] ); } calcRect(){ double arrowLeft = 0.0; double arrowTop = 0.0; double bodyTop = 0.0; double bodyLeft = 0.0; if(widget.attachRect.left > widget.width / 2 && ScreenUtil.screenWidth - widget.attachRect.right > widget.width / 2){ //判断是否可以在中间 arrowLeft = widget.attachRect.left + widget.attachRect.width / 2 - _arrowWidth / 2; bodyLeft = widget.attachRect.left + widget.attachRect.width / 2 - widget.width / 2; }else if(widget.attachRect.left < widget.width / 2){ //靠左 bodyLeft = 10.0; arrowLeft = bodyLeft + widget.radius; }else{ //靠右 bodyLeft = ScreenUtil.screenWidth - 10.0 - widget.width; arrowLeft = ScreenUtil.screenWidth - 10.0 - _arrowWidth - 5 - widget.radius; } if(isArrowUp){ arrowTop = widget.attachRect.bottom; bodyTop = arrowTop + _arrowHeight; }else{ arrowTop = widget.attachRect.top - _arrowHeight; bodyTop = widget.attachRect.top - widget.height - _arrowHeight; } left = bodyLeft; top = isArrowUp?arrowTop:bodyTop; _arrowRect = Rect.fromLTWH(arrowLeft - left, arrowTop - top, _arrowWidth, _arrowHeight); _bodyRect = Rect.fromLTWH(0.0, bodyTop - top, widget.width, widget.height); } } class ArrowCliper extends CustomClipper{ final bool isArrowUp; final Rect arrowRect; final Rect bodyRect; final double radius; const ArrowCliper({this.isArrowUp,this.arrowRect,this.bodyRect,this.radius = 13.0}); @override Path getClip(Size size) { Path path = new Path(); if(isArrowUp) { path.moveTo(arrowRect.left,arrowRect.bottom); //箭头 path.lineTo(arrowRect.left + arrowRect.width / 2, arrowRect.top); path.lineTo(arrowRect.right, arrowRect.bottom); path.lineTo(bodyRect.right - radius,bodyRect.top); //右上角 path.conicTo(bodyRect.right,bodyRect.top ,bodyRect.right,bodyRect.top + radius,1.0); path.lineTo(bodyRect.right,bodyRect.bottom - radius); //右下角 path.conicTo(bodyRect.right,bodyRect.bottom ,bodyRect.right -radius ,bodyRect.bottom,1.0); path.lineTo(bodyRect.left + radius, bodyRect.bottom); //左下角 path.conicTo(bodyRect.left,bodyRect.bottom ,bodyRect.left ,bodyRect.bottom - radius,1.0); path.lineTo(bodyRect.left, bodyRect.top + radius); //左上角 path.conicTo(bodyRect.left,bodyRect.top ,bodyRect.left + radius,bodyRect.top,1.0); }else{ path.moveTo(bodyRect.left + radius,bodyRect.top); path.lineTo(bodyRect.right - radius,bodyRect.top); //右上角 path.conicTo(bodyRect.right,bodyRect.top ,bodyRect.right,bodyRect.top + radius,1.0); path.lineTo(bodyRect.right,bodyRect.bottom - radius); //右下角 path.conicTo(bodyRect.right,bodyRect.bottom ,bodyRect.right -radius ,bodyRect.bottom,1.0); path.lineTo(arrowRect.right, arrowRect.top); //箭头 path.lineTo(arrowRect.left + arrowRect.width / 2, arrowRect.bottom); path.lineTo(arrowRect.left,arrowRect.top); path.lineTo(bodyRect.left + radius, bodyRect.bottom); //左下角 path.conicTo(bodyRect.left,bodyRect.bottom ,bodyRect.left ,bodyRect.bottom - radius,1.0); path.lineTo(bodyRect.left, bodyRect.top + radius); //左上角 path.conicTo(bodyRect.left,bodyRect.top ,bodyRect.left + radius,bodyRect.top,1.0); } path.close(); return path; } @override bool shouldReclip(ArrowCliper oldClipper) { return this.isArrowUp != oldClipper.isArrowUp || this.arrowRect != oldClipper.arrowRect || this.bodyRect != oldClipper.bodyRect; } }