cupertino_popover.dart 9.2 KB

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