cupertino_popover.dart 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440
  1. part of cool_ui;
  2. typedef BoolCallback = bool Function();
  3. class CupertinoPopoverButton extends StatelessWidget{
  4. final Widget child;
  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. final BoolCallback onTap;
  12. final BoxConstraints popoverConstraints;
  13. final Color barrierColor;
  14. CupertinoPopoverButton({
  15. @required this.child,
  16. this.popoverBuild,
  17. this.popoverColor=Colors.white,
  18. this.popoverWidth,
  19. this.popoverHeight,
  20. BoxConstraints popoverConstraints,
  21. this.onTap,
  22. this.transitionDuration=const Duration(milliseconds: 200),
  23. this.barrierColor = Colors.black54,
  24. this.radius=8.0}):
  25. assert(popoverBuild != null),
  26. this.popoverConstraints =
  27. (popoverWidth != null || popoverHeight != null)
  28. ? popoverConstraints?.tighten(width: popoverWidth, height: popoverHeight)
  29. ?? BoxConstraints.tightFor(width: popoverWidth, height: popoverHeight)
  30. : popoverConstraints;
  31. @override
  32. Widget build(BuildContext context) {
  33. // TODO: implement build
  34. return GestureDetector(
  35. behavior: HitTestBehavior.translucent,
  36. onTap: (){
  37. if(onTap != null && onTap()){
  38. return;
  39. }
  40. var offset = _WidgetUtil.getWidgetLocalToGlobal(context);
  41. var bounds = _WidgetUtil.getWidgetBounds(context);
  42. var body;
  43. showGeneralDialog(
  44. context: context,
  45. pageBuilder: (BuildContext buildContext, Animation<double> animation, Animation<double> secondaryAnimation) {
  46. return Builder(
  47. builder: (BuildContext context) {
  48. return Container();
  49. }
  50. );
  51. },
  52. barrierDismissible: true,
  53. barrierLabel: MaterialLocalizations.of(context).modalBarrierDismissLabel,
  54. barrierColor: this.barrierColor,
  55. transitionDuration: transitionDuration,
  56. transitionBuilder: (BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation, Widget child) {
  57. if(body == null){
  58. body = popoverBuild(context);
  59. }
  60. return FadeTransition(
  61. opacity: CurvedAnimation(
  62. parent: animation,
  63. curve: Curves.easeOut,
  64. ),
  65. child: CupertinoPopover(
  66. attachRect:Rect.fromLTWH(offset.dx, offset.dy, bounds.width, bounds.height),
  67. child: body,
  68. constraints:popoverConstraints,
  69. color: popoverColor,
  70. context: context,
  71. radius: radius,
  72. doubleAnimation: animation,
  73. ),
  74. );
  75. },);
  76. },
  77. child: child,
  78. );
  79. }
  80. }
  81. // ignore: must_be_immutable
  82. class CupertinoPopover extends StatefulWidget {
  83. final Rect attachRect;
  84. final Widget child;
  85. final Color color;
  86. final double radius;
  87. final Animation<double> doubleAnimation;
  88. BoxConstraints constraints;
  89. CupertinoPopover({
  90. @required this.attachRect,
  91. @required this.child,
  92. BoxConstraints constraints,
  93. this.color=Colors.white,
  94. @required BuildContext context,
  95. this.doubleAnimation,
  96. this.radius=8.0}):super(){
  97. BoxConstraints temp;
  98. if(constraints != null){
  99. temp = BoxConstraints(maxHeight:123.0,maxWidth:150.0).copyWith(
  100. minWidth: constraints.minWidth.isFinite?constraints.minWidth:null,
  101. minHeight: constraints.minHeight.isFinite?constraints.minHeight:null,
  102. maxWidth: constraints.maxWidth.isFinite?constraints.maxWidth:null,
  103. maxHeight: constraints.maxHeight.isFinite?constraints.maxHeight:null,
  104. );
  105. }else{
  106. temp=BoxConstraints(maxHeight:123.0,maxWidth:150.0);
  107. }
  108. this.constraints = temp.copyWith(maxHeight: temp.maxHeight + CupertinoPopoverState._arrowHeight);
  109. }
  110. @override
  111. CupertinoPopoverState createState() => new CupertinoPopoverState();
  112. @override
  113. void debugFillProperties(DiagnosticPropertiesBuilder properties) {
  114. super.debugFillProperties(properties);
  115. properties.add(DiagnosticsProperty<BoxConstraints>('constraints', constraints, showName: false));
  116. properties.add(DiagnosticsProperty<Color>('color', color, showName: false));
  117. properties.add(DiagnosticsProperty<double>('double', radius, showName: false));
  118. }
  119. }
  120. class CupertinoPopoverState extends State<CupertinoPopover> with TickerProviderStateMixin{
  121. static const double _arrowWidth = 12.0;
  122. static const double _arrowHeight = 8.0;
  123. // AnimationController animation;
  124. /// 是否箭头向上
  125. bool isArrowUp;
  126. @override
  127. void initState() {
  128. super.initState();
  129. }
  130. @override
  131. Widget build(BuildContext context) {
  132. return Stack(
  133. children: <Widget>[
  134. _CupertionPopoverPosition(
  135. attachRect: widget.attachRect,
  136. scale: widget.doubleAnimation,
  137. constraints: widget.constraints,
  138. child: _CupertionPopoverContext(
  139. attachRect: widget.attachRect,
  140. scale: widget.doubleAnimation,
  141. radius: widget.radius,
  142. color: widget.color,
  143. child: Material(child: widget.child),
  144. ),
  145. )
  146. ],
  147. );
  148. }
  149. }
  150. class _CupertionPopoverPosition extends SingleChildRenderObjectWidget{
  151. final Rect attachRect;
  152. final Animation<double> scale;
  153. final BoxConstraints constraints;
  154. _CupertionPopoverPosition({Widget child,this.attachRect,this.constraints,this.scale}):super(child:child);
  155. @override
  156. RenderObject createRenderObject(BuildContext context) =>_CupertionPopoverPositionRenderObject(
  157. attachRect:attachRect,
  158. constraints:constraints);
  159. @override
  160. void updateRenderObject(BuildContext context, _CupertionPopoverPositionRenderObject renderObject) {
  161. renderObject
  162. ..attachRect = attachRect
  163. ..additionalConstraints = constraints;
  164. }
  165. @override
  166. void debugFillProperties(DiagnosticPropertiesBuilder properties) {
  167. super.debugFillProperties(properties);
  168. properties.add(DiagnosticsProperty<BoxConstraints>('constraints', constraints, showName: false));
  169. }
  170. }
  171. class _CupertionPopoverPositionRenderObject extends RenderShiftedBox{
  172. Rect get attachRect => _attachRect;
  173. Rect _attachRect;
  174. set attachRect(Rect value) {
  175. if (_attachRect == value)
  176. return;
  177. _attachRect = value;
  178. markNeedsLayout();
  179. }
  180. BoxConstraints get additionalConstraints => _additionalConstraints;
  181. BoxConstraints _additionalConstraints;
  182. set additionalConstraints(BoxConstraints value) {
  183. if (_additionalConstraints == value)
  184. return;
  185. _additionalConstraints = value;
  186. markNeedsLayout();
  187. }
  188. _CupertionPopoverPositionRenderObject({RenderBox child,Rect attachRect,Color color,BoxConstraints constraints,Animation<double> scale}) : super(child){
  189. this._attachRect = attachRect;
  190. this._additionalConstraints = constraints;
  191. }
  192. @override
  193. void performLayout() {
  194. child.layout(_additionalConstraints.enforce(constraints), parentUsesSize: true);
  195. size = Size(constraints.maxWidth,constraints.maxHeight);
  196. final BoxParentData childParentData = child.parentData;
  197. childParentData.offset = calcOffset(child.size);
  198. }
  199. Offset calcOffset(Size size){
  200. double bodyLeft = 0.0;
  201. var isArrowUp = _ScreenUtil.getInstance().screenHeight > attachRect.bottom + size.height + CupertinoPopoverState._arrowHeight;
  202. if(attachRect.left > size.width / 2 &&
  203. _ScreenUtil.getInstance().screenWidth - attachRect.right > size.width / 2){ //判断是否可以在中间
  204. bodyLeft = attachRect.left + attachRect.width / 2 - size.width / 2;
  205. }else if(attachRect.left < size.width / 2){ //靠左
  206. bodyLeft = 10.0;
  207. }else{ //靠右
  208. bodyLeft = _ScreenUtil.getInstance().screenWidth - 10.0 - size.width;
  209. }
  210. if(isArrowUp){
  211. return Offset(bodyLeft,attachRect.bottom);
  212. }else{
  213. return Offset(bodyLeft,attachRect.top - size.height - CupertinoPopoverState._arrowHeight);
  214. }
  215. }
  216. @override
  217. void debugFillProperties(DiagnosticPropertiesBuilder properties) {
  218. super.debugFillProperties(properties);
  219. properties.add(DiagnosticsProperty<BoxConstraints>('additionalConstraints', additionalConstraints));
  220. }
  221. }
  222. class _CupertionPopoverContext extends SingleChildRenderObjectWidget{
  223. final Rect attachRect;
  224. final Color color;
  225. final Animation<double> scale;
  226. final double radius;
  227. _CupertionPopoverContext({Widget child,this.attachRect,this.color,this.scale,this.radius}):super(child:child);
  228. @override
  229. RenderObject createRenderObject(BuildContext context) => _CupertionPopoverContextRenderObject(
  230. attachRect: attachRect,
  231. color: color,
  232. scale: scale,
  233. radius: radius
  234. );
  235. @override
  236. void updateRenderObject(BuildContext context, _CupertionPopoverContextRenderObject renderObject) {
  237. renderObject
  238. ..attachRect = attachRect
  239. ..color = color
  240. ..scale = scale
  241. ..radius = radius;
  242. }
  243. }
  244. class _CupertionPopoverContextRenderObject extends RenderShiftedBox{
  245. Rect get attachRect => _attachRect;
  246. Rect _attachRect;
  247. set attachRect(Rect value) {
  248. if (_attachRect == value)
  249. return;
  250. _attachRect = value;
  251. markNeedsLayout();
  252. }
  253. Color get color => _color;
  254. Color _color;
  255. set color(Color value) {
  256. if (_color == value)
  257. return;
  258. _color = value;
  259. markNeedsLayout();
  260. }
  261. Animation<double> get scale => _scale;
  262. Animation<double> _scale;
  263. set scale(Animation<double> value) {
  264. if (_scale == value)
  265. return;
  266. _scale = value;
  267. markNeedsLayout();
  268. }
  269. double get radius => _radius;
  270. double _radius;
  271. set radius(double value) {
  272. if (_radius == value)
  273. return;
  274. _radius = value;
  275. markNeedsLayout();
  276. }
  277. _CupertionPopoverContextRenderObject({RenderBox child,Rect attachRect,Color color,Animation<double> scale,double radius}) : super(child){
  278. this._attachRect = attachRect;
  279. this._color = color;
  280. this._scale = scale;
  281. this._radius = radius;
  282. }
  283. @override
  284. void performLayout() {
  285. assert(constraints.maxHeight.isFinite);
  286. BoxConstraints childConstraints = BoxConstraints(maxHeight: constraints.maxHeight - CupertinoPopoverState._arrowHeight).enforce(constraints);
  287. child.layout(childConstraints, parentUsesSize: true);
  288. size = Size(child.size.width,child.size.height + CupertinoPopoverState._arrowHeight);
  289. final BoxParentData childParentData = child.parentData;
  290. var isArrowUp = _ScreenUtil.getInstance().screenHeight > attachRect.bottom + size.height + CupertinoPopoverState._arrowHeight;
  291. if(isArrowUp)
  292. {
  293. childParentData.offset = Offset(0.0, CupertinoPopoverState._arrowHeight);
  294. }
  295. }
  296. @override
  297. void paint(PaintingContext context, Offset offset) {
  298. // TODO: implement paint
  299. Matrix4 transform = Matrix4.identity();
  300. //
  301. var isArrowUp = _ScreenUtil.getInstance().screenHeight > attachRect.bottom + size.height + CupertinoPopoverState._arrowHeight;
  302. var arrowLeft =attachRect.left + attachRect.width / 2 - CupertinoPopoverState._arrowWidth / 2 - offset.dx;
  303. var translation = Offset(arrowLeft + CupertinoPopoverState._arrowWidth / 2,isArrowUp?0.0:size.height);
  304. transform.translate(translation.dx, translation.dy);
  305. transform.scale(scale.value, scale.value, 1.0);
  306. transform.translate(-translation.dx, -translation.dy);
  307. Rect arrowRect = Rect.fromLTWH(
  308. arrowLeft,
  309. isArrowUp?0.0:child.size.height,
  310. CupertinoPopoverState._arrowWidth,
  311. CupertinoPopoverState._arrowHeight);
  312. Rect bodyRect = Offset(0.0, isArrowUp?CupertinoPopoverState._arrowHeight:0.0) & child.size;
  313. context.pushClipPath(needsCompositing,
  314. offset,offset & size,
  315. getClip(size,isArrowUp,arrowRect,bodyRect),(context,offset){
  316. context.pushTransform(needsCompositing, offset, transform,(context,offset){
  317. final Paint backgroundPaint = Paint();
  318. backgroundPaint.color = color;
  319. context.canvas.drawRect(offset & size, backgroundPaint);
  320. super.paint(context,offset);
  321. });
  322. });
  323. }
  324. Path getClip(Size size,bool isArrowUp,Rect arrowRect,Rect bodyRect) {
  325. Path path = new Path();
  326. if(isArrowUp)
  327. {
  328. path.moveTo(arrowRect.left,arrowRect.bottom); //箭头
  329. path.lineTo(arrowRect.left + arrowRect.width / 2, arrowRect.top);
  330. path.lineTo(arrowRect.right, arrowRect.bottom);
  331. path.lineTo(bodyRect.right - radius,bodyRect.top); //右上角
  332. path.conicTo(bodyRect.right,bodyRect.top
  333. ,bodyRect.right,bodyRect.top + radius,1.0);
  334. path.lineTo(bodyRect.right,bodyRect.bottom - radius); //右下角
  335. path.conicTo(bodyRect.right,bodyRect.bottom
  336. ,bodyRect.right -radius ,bodyRect.bottom,1.0);
  337. path.lineTo(bodyRect.left + radius, bodyRect.bottom); //左下角
  338. path.conicTo(bodyRect.left,bodyRect.bottom
  339. ,bodyRect.left ,bodyRect.bottom - radius,1.0);
  340. path.lineTo(bodyRect.left, bodyRect.top + radius); //左上角
  341. path.conicTo(bodyRect.left,bodyRect.top
  342. ,bodyRect.left + radius,bodyRect.top,1.0);
  343. }else{
  344. path.moveTo(bodyRect.left + radius,bodyRect.top);
  345. path.lineTo(bodyRect.right - radius,bodyRect.top); //右上角
  346. path.conicTo(bodyRect.right,bodyRect.top
  347. ,bodyRect.right,bodyRect.top + radius,1.0);
  348. path.lineTo(bodyRect.right,bodyRect.bottom - radius); //右下角
  349. path.conicTo(bodyRect.right,bodyRect.bottom
  350. ,bodyRect.right -radius ,bodyRect.bottom,1.0);
  351. path.lineTo(arrowRect.right, arrowRect.top); //箭头
  352. path.lineTo(arrowRect.left + arrowRect.width / 2, arrowRect.bottom);
  353. path.lineTo(arrowRect.left,arrowRect.top);
  354. path.lineTo(bodyRect.left + radius, bodyRect.bottom); //左下角
  355. path.conicTo(bodyRect.left,bodyRect.bottom
  356. ,bodyRect.left ,bodyRect.bottom - radius,1.0);
  357. path.lineTo(bodyRect.left, bodyRect.top + radius); //左上角
  358. path.conicTo(bodyRect.left,bodyRect.top
  359. ,bodyRect.left + radius,bodyRect.top,1.0);
  360. }
  361. path.close();
  362. return path;
  363. }
  364. }