cupertino_popover.dart 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270
  1. part of cool_ui;
  2. class CupertinoPopoverButton extends StatelessWidget{
  3. final Widget child;
  4. final Widget popoverBody;
  5. final WidgetBuilder popoverBuild;
  6. final double popoverWidth;
  7. final double popoverHeight;
  8. final Color popoverColor;
  9. final double radius;
  10. final Duration transitionDuration;
  11. const CupertinoPopoverButton({
  12. @required this.child,
  13. @Deprecated(
  14. '建议不要直接使用popoverBody,而是使用popoverBuild.'
  15. )
  16. this.popoverBody,
  17. this.popoverBuild,
  18. this.popoverColor=Colors.white,
  19. @required this.popoverWidth,
  20. @required this.popoverHeight,
  21. this.transitionDuration=const Duration(milliseconds: 200),
  22. this.radius=8.0}):
  23. assert(popoverBody != null || popoverBuild != null),
  24. assert(!(popoverBody != null && popoverBuild != null));
  25. @override
  26. Widget build(BuildContext context) {
  27. // TODO: implement build
  28. return GestureDetector(
  29. behavior: HitTestBehavior.translucent,
  30. onTap: (){
  31. var offset = WidgetUtils.getWidgetLocalToGlobal(context);
  32. var bounds = WidgetUtils.getWidgetBounds(context);
  33. var body = popoverBody;
  34. showGeneralDialog(
  35. context: context,
  36. pageBuilder: (BuildContext buildContext, Animation<double> animation, Animation<double> secondaryAnimation) {
  37. return Builder(
  38. builder: (BuildContext context) {
  39. return Container();
  40. }
  41. );
  42. },
  43. barrierDismissible: true,
  44. barrierLabel: MaterialLocalizations.of(context).modalBarrierDismissLabel,
  45. barrierColor: Colors.black54,
  46. transitionDuration: transitionDuration,
  47. transitionBuilder: (BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation, Widget child) {
  48. if(body == null){
  49. body = popoverBuild(context);
  50. }
  51. return FadeTransition(
  52. opacity: CurvedAnimation(
  53. parent: animation,
  54. curve: Curves.easeOut,
  55. ),
  56. child: CupertinoPopover(
  57. attachRect:Rect.fromLTWH(offset.dx, offset.dy, bounds.width, bounds.height),
  58. child: body,
  59. width: popoverWidth,
  60. height: popoverHeight,
  61. color: popoverColor,
  62. context: context,
  63. radius: radius,
  64. doubleAnimation: animation,
  65. ),
  66. );
  67. },);
  68. },
  69. child: child,
  70. );
  71. }
  72. }
  73. class CupertinoPopover extends StatefulWidget {
  74. final Rect attachRect;
  75. final Widget child;
  76. final double width;
  77. final double height;
  78. final Color color;
  79. final double radius;
  80. final Animation<double> doubleAnimation;
  81. CupertinoPopover({
  82. @required this.attachRect,
  83. @required this.child,
  84. @required this.width,
  85. @required this.height,
  86. this.color=Colors.white,
  87. @required BuildContext context,
  88. this.doubleAnimation,
  89. this.radius=8.0}):super(){
  90. ScreenUtil.getInstance().init(context);
  91. }
  92. @override
  93. CupertinoPopoverState createState() => new CupertinoPopoverState();
  94. }
  95. class CupertinoPopoverState extends State<CupertinoPopover> with TickerProviderStateMixin{
  96. static const double _arrowWidth = 12.0;
  97. static const double _arrowHeight = 8.0;
  98. double left;
  99. double top;
  100. Rect _arrowRect;
  101. Rect _bodyRect;
  102. // AnimationController animation;
  103. /// 是否箭头向上
  104. bool isArrowUp;
  105. @override
  106. void initState() {
  107. // TODO: implement initState
  108. isArrowUp = ScreenUtil.screenHeight > widget.attachRect.bottom + widget.height + _arrowWidth;
  109. super.initState();
  110. calcRect();
  111. // animation = new AnimationController(
  112. // duration: widget.transitionDuration,
  113. // vsync: this
  114. // );
  115. // // Tween({T begin, T end }):创建tween(补间)
  116. // doubleAnimation = new Tween<double>(begin: 0.0, end: 1.0).animate(animation)..addListener((){
  117. // setState((){});
  118. // });
  119. // animation.forward(from: 0.0);
  120. }
  121. @override
  122. Widget build(BuildContext context) {
  123. var bodyMiddleX = _bodyRect.left + _bodyRect.width / 2; // 计算Body的X轴中间点
  124. var arrowMiddleX = _arrowRect.left + _arrowRect.width /2; //计算箭头的X轴中间点
  125. var leftOffset = (arrowMiddleX - bodyMiddleX) * (1 - widget.doubleAnimation.value); //计算X轴缩小的偏移值
  126. return Stack(
  127. children: <Widget>[
  128. Positioned(
  129. left:left + leftOffset,
  130. top:top,
  131. child:ScaleTransition(
  132. alignment: isArrowUp?Alignment.topCenter:Alignment.bottomCenter,
  133. scale: widget.doubleAnimation,
  134. child: ClipPath(
  135. clipper:ArrowCliper(
  136. arrowRect:_arrowRect,
  137. bodyRect: _bodyRect,
  138. isArrowUp: isArrowUp,
  139. radius: widget.radius
  140. ),
  141. child: Container(
  142. padding: EdgeInsets.only(top:isArrowUp?_arrowHeight:0.0),
  143. color: Colors.white,
  144. width: widget.width,
  145. height: _bodyRect.height + _arrowHeight,
  146. child: Material(child: widget.child)
  147. ),),
  148. ),
  149. )
  150. ]
  151. );
  152. }
  153. calcRect(){
  154. double arrowLeft = 0.0;
  155. double arrowTop = 0.0;
  156. double bodyTop = 0.0;
  157. double bodyLeft = 0.0;
  158. arrowLeft = widget.attachRect.left + widget.attachRect.width / 2 - _arrowWidth / 2;
  159. if(widget.attachRect.left > widget.width / 2 &&
  160. ScreenUtil.screenWidth - widget.attachRect.right > widget.width / 2){ //判断是否可以在中间
  161. bodyLeft = widget.attachRect.left + widget.attachRect.width / 2 - widget.width / 2;
  162. }else if(widget.attachRect.left < widget.width / 2){ //靠左
  163. bodyLeft = 10.0;
  164. }else{ //靠右
  165. bodyLeft = ScreenUtil.screenWidth - 10.0 - widget.width;
  166. }
  167. if(isArrowUp){
  168. arrowTop = widget.attachRect.bottom;
  169. bodyTop = arrowTop + _arrowHeight;
  170. }else{
  171. arrowTop = widget.attachRect.top - _arrowHeight;
  172. bodyTop = widget.attachRect.top - widget.height - _arrowHeight;
  173. }
  174. left = bodyLeft;
  175. top = isArrowUp?arrowTop:bodyTop;
  176. _arrowRect = Rect.fromLTWH(arrowLeft - left, arrowTop - top, _arrowWidth, _arrowHeight);
  177. _bodyRect = Rect.fromLTWH(0.0, bodyTop - top, widget.width, widget.height);
  178. }
  179. }
  180. class ArrowCliper extends CustomClipper<Path>{
  181. final bool isArrowUp;
  182. final Rect arrowRect;
  183. final Rect bodyRect;
  184. final double radius;
  185. const ArrowCliper({this.isArrowUp,this.arrowRect,this.bodyRect,this.radius = 13.0});
  186. @override
  187. Path getClip(Size size) {
  188. Path path = new Path();
  189. if(isArrowUp)
  190. {
  191. path.moveTo(arrowRect.left,arrowRect.bottom); //箭头
  192. path.lineTo(arrowRect.left + arrowRect.width / 2, arrowRect.top);
  193. path.lineTo(arrowRect.right, arrowRect.bottom);
  194. path.lineTo(bodyRect.right - radius,bodyRect.top); //右上角
  195. path.conicTo(bodyRect.right,bodyRect.top
  196. ,bodyRect.right,bodyRect.top + radius,1.0);
  197. path.lineTo(bodyRect.right,bodyRect.bottom - radius); //右下角
  198. path.conicTo(bodyRect.right,bodyRect.bottom
  199. ,bodyRect.right -radius ,bodyRect.bottom,1.0);
  200. path.lineTo(bodyRect.left + radius, bodyRect.bottom); //左下角
  201. path.conicTo(bodyRect.left,bodyRect.bottom
  202. ,bodyRect.left ,bodyRect.bottom - radius,1.0);
  203. path.lineTo(bodyRect.left, bodyRect.top + radius); //左上角
  204. path.conicTo(bodyRect.left,bodyRect.top
  205. ,bodyRect.left + radius,bodyRect.top,1.0);
  206. }else{
  207. path.moveTo(bodyRect.left + radius,bodyRect.top);
  208. path.lineTo(bodyRect.right - radius,bodyRect.top); //右上角
  209. path.conicTo(bodyRect.right,bodyRect.top
  210. ,bodyRect.right,bodyRect.top + radius,1.0);
  211. path.lineTo(bodyRect.right,bodyRect.bottom - radius); //右下角
  212. path.conicTo(bodyRect.right,bodyRect.bottom
  213. ,bodyRect.right -radius ,bodyRect.bottom,1.0);
  214. path.lineTo(arrowRect.right, arrowRect.top); //箭头
  215. path.lineTo(arrowRect.left + arrowRect.width / 2, arrowRect.bottom);
  216. path.lineTo(arrowRect.left,arrowRect.top);
  217. path.lineTo(bodyRect.left + radius, bodyRect.bottom); //左下角
  218. path.conicTo(bodyRect.left,bodyRect.bottom
  219. ,bodyRect.left ,bodyRect.bottom - radius,1.0);
  220. path.lineTo(bodyRect.left, bodyRect.top + radius); //左上角
  221. path.conicTo(bodyRect.left,bodyRect.top
  222. ,bodyRect.left + radius,bodyRect.top,1.0);
  223. }
  224. path.close();
  225. return path;
  226. }
  227. @override
  228. bool shouldReclip(ArrowCliper oldClipper) {
  229. return this.isArrowUp != oldClipper.isArrowUp || this.arrowRect != oldClipper.arrowRect || this.bodyRect != oldClipper.bodyRect;
  230. }
  231. }