cupertino_popover.dart 8.8 KB

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