cupertino_popover.dart 9.3 KB

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