part of cool_ui; typedef BoolCallback = bool Function(); class CupertinoPopoverButton extends StatelessWidget{ final Widget child; final Widget popoverBody; final WidgetBuilder popoverBuild; final double popoverWidth; final double popoverHeight; final Color popoverColor; final double radius; final Duration transitionDuration; final BoolCallback onTap; final BoxConstraints popoverConstraints; CupertinoPopoverButton({ @required this.child, @Deprecated( '建议不要直接使用popoverBody,而是使用popoverBuild.' ) this.popoverBody, this.popoverBuild, this.popoverColor=Colors.white, this.popoverWidth, this.popoverHeight, BoxConstraints popoverConstraints, this.onTap, this.transitionDuration=const Duration(milliseconds: 200), this.radius=8.0}): assert(popoverBody != null || popoverBuild != null), assert(!(popoverBody != null && popoverBuild != null)), this.popoverConstraints = (popoverWidth != null || popoverHeight != null) ? popoverConstraints?.tighten(width: popoverWidth, height: popoverHeight) ?? BoxConstraints.tightFor(width: popoverWidth, height: popoverHeight) : popoverConstraints; @override Widget build(BuildContext context) { // TODO: implement build return GestureDetector( behavior: HitTestBehavior.translucent, onTap: (){ if(onTap != null && onTap()){ return; } var offset = WidgetUtil.getWidgetLocalToGlobal(context); var bounds = WidgetUtil.getWidgetBounds(context); var body = popoverBody; showGeneralDialog( context: context, pageBuilder: (BuildContext buildContext, Animation<double> animation, Animation<double> secondaryAnimation) { return Builder( builder: (BuildContext context) { return Container(); } ); }, barrierDismissible: true, barrierLabel: MaterialLocalizations.of(context).modalBarrierDismissLabel, barrierColor: Colors.black54, transitionDuration: transitionDuration, transitionBuilder: (BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation, Widget child) { if(body == null){ body = popoverBuild(context); } return FadeTransition( opacity: CurvedAnimation( parent: animation, curve: Curves.easeOut, ), child: CupertinoPopover( attachRect:Rect.fromLTWH(offset.dx, offset.dy, bounds.width, bounds.height), child: body, constraints:popoverConstraints, color: popoverColor, context: context, radius: radius, doubleAnimation: animation, ), ); },); }, child: child, ); } } class CupertinoPopover extends StatefulWidget { final Rect attachRect; final Widget child; final Color color; final double radius; final Animation<double> doubleAnimation; BoxConstraints constraints; CupertinoPopover({ @required this.attachRect, @required this.child, BoxConstraints constraints, this.color=Colors.white, @required BuildContext context, this.doubleAnimation, this.radius=8.0}):super(){ BoxConstraints temp; if(constraints != null){ temp = BoxConstraints(maxHeight:123.0,maxWidth:150.0).copyWith( minWidth: constraints.minWidth.isFinite?constraints.minWidth:null, minHeight: constraints.minHeight.isFinite?constraints.minHeight:null, maxWidth: constraints.maxWidth.isFinite?constraints.maxWidth:null, maxHeight: constraints.maxHeight.isFinite?constraints.maxHeight:null, ); }else{ temp=BoxConstraints(maxHeight:123.0,maxWidth:150.0); } this.constraints = temp.copyWith(maxHeight: temp.maxHeight + CupertinoPopoverState._arrowHeight); } @override CupertinoPopoverState createState() => new CupertinoPopoverState(); @override void debugFillProperties(DiagnosticPropertiesBuilder properties) { super.debugFillProperties(properties); properties.add(DiagnosticsProperty<BoxConstraints>('constraints', constraints, showName: false)); properties.add(DiagnosticsProperty<Color>('color', color, showName: false)); properties.add(DiagnosticsProperty<double>('double', radius, showName: false)); } } class CupertinoPopoverState extends State<CupertinoPopover> with TickerProviderStateMixin{ static const double _arrowWidth = 12.0; static const double _arrowHeight = 8.0; // AnimationController animation; /// 是否箭头向上 bool isArrowUp; @override void initState() { super.initState(); } @override Widget build(BuildContext context) { return Stack( children: <Widget>[ _CupertionPopoverPosition( attachRect: widget.attachRect, scale: widget.doubleAnimation, constraints: widget.constraints, child: _CupertionPopoverContext( attachRect: widget.attachRect, scale: widget.doubleAnimation, radius: widget.radius, color: widget.color, child: Material(child: widget.child), ), ) ], ); } } class _CupertionPopoverPosition extends SingleChildRenderObjectWidget{ final Rect attachRect; final Animation<double> scale; final BoxConstraints constraints; _CupertionPopoverPosition({Widget child,this.attachRect,this.constraints,this.scale}):super(child:child); @override RenderObject createRenderObject(BuildContext context) =>_CupertionPopoverPositionRenderObject( attachRect:attachRect, constraints:constraints); @override void updateRenderObject(BuildContext context, _CupertionPopoverPositionRenderObject renderObject) { renderObject ..attachRect = attachRect ..additionalConstraints = constraints; } @override void debugFillProperties(DiagnosticPropertiesBuilder properties) { super.debugFillProperties(properties); properties.add(DiagnosticsProperty<BoxConstraints>('constraints', constraints, showName: false)); } } class _CupertionPopoverPositionRenderObject extends RenderShiftedBox{ Rect get attachRect => _attachRect; Rect _attachRect; set attachRect(Rect value) { if (_attachRect == value) return; _attachRect = value; markNeedsLayout(); } BoxConstraints get additionalConstraints => _additionalConstraints; BoxConstraints _additionalConstraints; set additionalConstraints(BoxConstraints value) { if (_additionalConstraints == value) return; _additionalConstraints = value; markNeedsLayout(); } _CupertionPopoverPositionRenderObject({RenderBox child,Rect attachRect,Color color,BoxConstraints constraints,Animation<double> scale}) : super(child){ this._attachRect = attachRect; this._additionalConstraints = constraints; } @override void performLayout() { child.layout(_additionalConstraints.enforce(constraints), parentUsesSize: true); size = Size(constraints.maxWidth,constraints.maxHeight); final BoxParentData childParentData = child.parentData; childParentData.offset = calcOffset(child.size); } Offset calcOffset(Size size){ double bodyLeft = 0.0; var isArrowUp = ScreenUtil.singleton.screenHeight > attachRect.bottom + size.height + CupertinoPopoverState._arrowHeight; if(attachRect.left > size.width / 2 && ScreenUtil.singleton.screenWidth - attachRect.right > size.width / 2){ //判断是否可以在中间 bodyLeft = attachRect.left + attachRect.width / 2 - size.width / 2; }else if(attachRect.left < size.width / 2){ //靠左 bodyLeft = 10.0; }else{ //靠右 bodyLeft = ScreenUtil.singleton.screenWidth - 10.0 - size.width; } if(isArrowUp){ return Offset(bodyLeft,attachRect.bottom); }else{ return Offset(bodyLeft,attachRect.top - size.height - CupertinoPopoverState._arrowHeight); } } @override void debugFillProperties(DiagnosticPropertiesBuilder properties) { super.debugFillProperties(properties); properties.add(DiagnosticsProperty<BoxConstraints>('additionalConstraints', additionalConstraints)); } } class _CupertionPopoverContext extends SingleChildRenderObjectWidget{ final Rect attachRect; final Color color; final Animation<double> scale; final double radius; _CupertionPopoverContext({Widget child,this.attachRect,this.color,this.scale,this.radius}):super(child:child); @override RenderObject createRenderObject(BuildContext context) => _CupertionPopoverContextRenderObject( attachRect: attachRect, color: color, scale: scale, radius: radius ); @override void updateRenderObject(BuildContext context, _CupertionPopoverContextRenderObject renderObject) { renderObject ..attachRect = attachRect ..color = color ..scale = scale ..radius = radius; } } class _CupertionPopoverContextRenderObject extends RenderShiftedBox{ Rect get attachRect => _attachRect; Rect _attachRect; set attachRect(Rect value) { if (_attachRect == value) return; _attachRect = value; markNeedsLayout(); } Color get color => _color; Color _color; set color(Color value) { if (_color == value) return; _color = value; markNeedsLayout(); } Animation<double> get scale => _scale; Animation<double> _scale; set scale(Animation<double> value) { if (_scale == value) return; _scale = value; markNeedsLayout(); } double get radius => _radius; double _radius; set radius(double value) { if (_radius == value) return; _radius = value; markNeedsLayout(); } _CupertionPopoverContextRenderObject({RenderBox child,Rect attachRect,Color color,Animation<double> scale,double radius}) : super(child){ this._attachRect = attachRect; this._color = color; this._scale = scale; this._radius = radius; } @override void performLayout() { assert(constraints.maxHeight.isFinite); BoxConstraints childConstraints = BoxConstraints(maxHeight: constraints.maxHeight - CupertinoPopoverState._arrowHeight).enforce(constraints); child.layout(childConstraints, parentUsesSize: true); size = Size(child.size.width,child.size.height + CupertinoPopoverState._arrowHeight); final BoxParentData childParentData = child.parentData; var isArrowUp = ScreenUtil.singleton.screenHeight > attachRect.bottom + size.height + CupertinoPopoverState._arrowHeight; if(isArrowUp) { childParentData.offset = Offset(0.0, CupertinoPopoverState._arrowHeight); } } @override void paint(PaintingContext context, Offset offset) { // TODO: implement paint Matrix4 transform = Matrix4.identity(); // var isArrowUp = ScreenUtil.singleton.screenHeight > attachRect.bottom + size.height + CupertinoPopoverState._arrowHeight; var arrowLeft =attachRect.left + attachRect.width / 2 - CupertinoPopoverState._arrowWidth / 2 - offset.dx; var translation = Offset(arrowLeft + CupertinoPopoverState._arrowWidth / 2,isArrowUp?0.0:size.height); transform.translate(translation.dx, translation.dy); transform.scale(scale.value, scale.value, 1.0); transform.translate(-translation.dx, -translation.dy); Rect arrowRect = Rect.fromLTWH( arrowLeft, isArrowUp?0.0:child.size.height, CupertinoPopoverState._arrowWidth, CupertinoPopoverState._arrowHeight); Rect bodyRect = Offset(0.0, isArrowUp?CupertinoPopoverState._arrowHeight:0.0) & child.size; context.pushClipPath(needsCompositing, offset,offset & size, getClip(size,isArrowUp,arrowRect,bodyRect),(context,offset){ context.pushTransform(needsCompositing, offset, transform,(context,offset){ final Paint backgroundPaint = Paint(); backgroundPaint.color = color; context.canvas.drawRect(offset & size, backgroundPaint); super.paint(context,offset); }); }); } Path getClip(Size size,bool isArrowUp,Rect arrowRect,Rect bodyRect) { 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; } }