cupertino_popover.dart 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262
  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. double left;
  92. double top;
  93. Rect _arrowRect;
  94. Rect _bodyRect;
  95. Animation<double> doubleAnimation;
  96. AnimationController animation;
  97. /// 是否箭头向上
  98. bool isArrowUp;
  99. @override
  100. void initState() {
  101. // TODO: implement initState
  102. isArrowUp = ScreenUtil.screenHeight > widget.attachRect.bottom + widget.height + _arrowWidth;
  103. super.initState();
  104. calcRect();
  105. animation = new AnimationController(
  106. duration: widget.transitionDuration,
  107. vsync: this
  108. );
  109. // Tween({T begin, T end }):创建tween(补间)
  110. doubleAnimation = new Tween<double>(begin: 0.0, end: 1.0).animate(animation)..addListener((){
  111. setState((){});
  112. });
  113. animation.forward(from: 0.0);
  114. }
  115. @override
  116. Widget build(BuildContext context) {
  117. var bodyMiddleX = _bodyRect.left + _bodyRect.width / 2; // 计算Body的X轴中间点
  118. var arrowMiddleX = _arrowRect.left + _arrowRect.width /2; //计算箭头的X轴中间点
  119. var leftOffset = (arrowMiddleX - bodyMiddleX) * (1 - doubleAnimation.value); //计算X轴缩小的偏移值
  120. return Stack(
  121. children: <Widget>[
  122. Positioned(
  123. left:left + leftOffset,
  124. top:top,
  125. child:ScaleTransition(
  126. alignment: isArrowUp?Alignment.topCenter:Alignment.bottomCenter,
  127. scale: doubleAnimation,
  128. child: ClipPath(
  129. clipper:ArrowCliper(
  130. arrowRect:_arrowRect,
  131. bodyRect: _bodyRect,
  132. isArrowUp: isArrowUp,
  133. radius: widget.radius
  134. ),
  135. child: Container(
  136. padding: EdgeInsets.only(top:isArrowUp?_arrowHeight:0.0),
  137. color: Colors.white,
  138. width: widget.width,
  139. height: _bodyRect.height + _arrowHeight,
  140. child: Material(child: widget.child)
  141. ),),
  142. ),
  143. )
  144. ]
  145. );
  146. }
  147. calcRect(){
  148. double arrowLeft = 0.0;
  149. double arrowTop = 0.0;
  150. double bodyTop = 0.0;
  151. double bodyLeft = 0.0;
  152. if(widget.attachRect.left > widget.width / 2 && ScreenUtil.screenWidth - widget.attachRect.right > widget.width / 2){ //判断是否可以在中间
  153. arrowLeft = widget.attachRect.left + widget.attachRect.width / 2 - _arrowWidth / 2;
  154. bodyLeft = widget.attachRect.left + widget.attachRect.width / 2 - widget.width / 2;
  155. }else if(widget.attachRect.left < widget.width / 2){ //靠左
  156. bodyLeft = 10.0;
  157. arrowLeft = bodyLeft + widget.radius;
  158. }else{ //靠右
  159. bodyLeft = ScreenUtil.screenWidth - 10.0 - widget.width;
  160. arrowLeft = ScreenUtil.screenWidth - 10.0 - _arrowWidth - 5 - widget.radius;
  161. }
  162. if(isArrowUp){
  163. arrowTop = widget.attachRect.bottom;
  164. bodyTop = arrowTop + _arrowHeight;
  165. }else{
  166. arrowTop = widget.attachRect.top - _arrowHeight;
  167. bodyTop = widget.attachRect.top - widget.height - _arrowHeight;
  168. }
  169. left = bodyLeft;
  170. top = isArrowUp?arrowTop:bodyTop;
  171. _arrowRect = Rect.fromLTWH(arrowLeft - left, arrowTop - top, _arrowWidth, _arrowHeight);
  172. _bodyRect = Rect.fromLTWH(0.0, bodyTop - top, widget.width, widget.height);
  173. }
  174. }
  175. class ArrowCliper extends CustomClipper<Path>{
  176. final bool isArrowUp;
  177. final Rect arrowRect;
  178. final Rect bodyRect;
  179. final double radius;
  180. const ArrowCliper({this.isArrowUp,this.arrowRect,this.bodyRect,this.radius = 13.0});
  181. @override
  182. Path getClip(Size size) {
  183. Path path = new Path();
  184. if(isArrowUp)
  185. {
  186. path.moveTo(arrowRect.left,arrowRect.bottom); //箭头
  187. path.lineTo(arrowRect.left + arrowRect.width / 2, arrowRect.top);
  188. path.lineTo(arrowRect.right, arrowRect.bottom);
  189. path.lineTo(bodyRect.right - radius,bodyRect.top); //右上角
  190. path.conicTo(bodyRect.right,bodyRect.top
  191. ,bodyRect.right,bodyRect.top + radius,1.0);
  192. path.lineTo(bodyRect.right,bodyRect.bottom - radius); //右下角
  193. path.conicTo(bodyRect.right,bodyRect.bottom
  194. ,bodyRect.right -radius ,bodyRect.bottom,1.0);
  195. path.lineTo(bodyRect.left + radius, bodyRect.bottom); //左下角
  196. path.conicTo(bodyRect.left,bodyRect.bottom
  197. ,bodyRect.left ,bodyRect.bottom - radius,1.0);
  198. path.lineTo(bodyRect.left, bodyRect.top + radius); //左上角
  199. path.conicTo(bodyRect.left,bodyRect.top
  200. ,bodyRect.left + radius,bodyRect.top,1.0);
  201. }else{
  202. path.moveTo(bodyRect.left + radius,bodyRect.top);
  203. path.lineTo(bodyRect.right - radius,bodyRect.top); //右上角
  204. path.conicTo(bodyRect.right,bodyRect.top
  205. ,bodyRect.right,bodyRect.top + radius,1.0);
  206. path.lineTo(bodyRect.right,bodyRect.bottom - radius); //右下角
  207. path.conicTo(bodyRect.right,bodyRect.bottom
  208. ,bodyRect.right -radius ,bodyRect.bottom,1.0);
  209. path.lineTo(arrowRect.right, arrowRect.top); //箭头
  210. path.lineTo(arrowRect.left + arrowRect.width / 2, arrowRect.bottom);
  211. path.lineTo(arrowRect.left,arrowRect.top);
  212. path.lineTo(bodyRect.left + radius, bodyRect.bottom); //左下角
  213. path.conicTo(bodyRect.left,bodyRect.bottom
  214. ,bodyRect.left ,bodyRect.bottom - radius,1.0);
  215. path.lineTo(bodyRect.left, bodyRect.top + radius); //左上角
  216. path.conicTo(bodyRect.left,bodyRect.top
  217. ,bodyRect.left + radius,bodyRect.top,1.0);
  218. }
  219. path.close();
  220. return path;
  221. }
  222. @override
  223. bool shouldReclip(ArrowCliper oldClipper) {
  224. return this.isArrowUp != oldClipper.isArrowUp || this.arrowRect != oldClipper.arrowRect || this.bodyRect != oldClipper.bodyRect;
  225. }
  226. }