cupertino_popover.dart 14 KB

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