cupertino_popover.dart 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444
  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. ScreenUtil.getInstance().init(context);
  101. BoxConstraints temp = null;
  102. if(constraints != null){
  103. temp = BoxConstraints(maxHeight:123.0,maxWidth:150.0).copyWith(
  104. minWidth: constraints.minWidth.isFinite?constraints.minWidth:null,
  105. minHeight: constraints.minHeight.isFinite?constraints.minHeight:null,
  106. maxWidth: constraints.maxWidth.isFinite?constraints.maxWidth:null,
  107. maxHeight: constraints.maxHeight.isFinite?constraints.maxHeight:null,
  108. );
  109. }else{
  110. temp=BoxConstraints(maxHeight:123.0,maxWidth:150.0);
  111. }
  112. this.constraints = temp.copyWith(maxHeight: temp.maxHeight + CupertinoPopoverState._arrowHeight);
  113. }
  114. @override
  115. CupertinoPopoverState createState() => new CupertinoPopoverState();
  116. @override
  117. void debugFillProperties(DiagnosticPropertiesBuilder properties) {
  118. super.debugFillProperties(properties);
  119. properties.add(DiagnosticsProperty<BoxConstraints>('constraints', constraints, showName: false));
  120. properties.add(DiagnosticsProperty<Color>('color', color, showName: false));
  121. properties.add(DiagnosticsProperty<double>('double', radius, showName: false));
  122. }
  123. }
  124. class CupertinoPopoverState extends State<CupertinoPopover> with TickerProviderStateMixin{
  125. static const double _arrowWidth = 12.0;
  126. static const double _arrowHeight = 8.0;
  127. // AnimationController animation;
  128. /// 是否箭头向上
  129. bool isArrowUp;
  130. @override
  131. void initState() {
  132. super.initState();
  133. }
  134. @override
  135. Widget build(BuildContext context) {
  136. return Stack(
  137. children: <Widget>[
  138. _CupertionPopoverPosition(
  139. attachRect: widget.attachRect,
  140. scale: widget.doubleAnimation,
  141. constraints: widget.constraints,
  142. child: _CupertionPopoverContext(
  143. attachRect: widget.attachRect,
  144. scale: widget.doubleAnimation,
  145. radius: widget.radius,
  146. color: widget.color,
  147. child: Material(child: widget.child),
  148. ),
  149. )
  150. ],
  151. );
  152. }
  153. }
  154. class _CupertionPopoverPosition extends SingleChildRenderObjectWidget{
  155. final Rect attachRect;
  156. final Animation<double> scale;
  157. final BoxConstraints constraints;
  158. _CupertionPopoverPosition({Widget child,this.attachRect,this.constraints,this.scale}):super(child:child);
  159. @override
  160. RenderObject createRenderObject(BuildContext context) =>_CupertionPopoverPositionRenderObject(
  161. attachRect:attachRect,
  162. constraints:constraints);
  163. @override
  164. void updateRenderObject(BuildContext context, _CupertionPopoverPositionRenderObject renderObject) {
  165. renderObject
  166. ..attachRect = attachRect
  167. ..additionalConstraints = constraints;
  168. }
  169. @override
  170. void debugFillProperties(DiagnosticPropertiesBuilder properties) {
  171. super.debugFillProperties(properties);
  172. properties.add(DiagnosticsProperty<BoxConstraints>('constraints', constraints, showName: false));
  173. }
  174. }
  175. class _CupertionPopoverPositionRenderObject extends RenderShiftedBox{
  176. Rect get attachRect => _attachRect;
  177. Rect _attachRect;
  178. set attachRect(Rect value) {
  179. if (_attachRect == value)
  180. return;
  181. _attachRect = value;
  182. markNeedsLayout();
  183. }
  184. BoxConstraints get additionalConstraints => _additionalConstraints;
  185. BoxConstraints _additionalConstraints;
  186. set additionalConstraints(BoxConstraints value) {
  187. if (_additionalConstraints == value)
  188. return;
  189. _additionalConstraints = value;
  190. markNeedsLayout();
  191. }
  192. _CupertionPopoverPositionRenderObject({RenderBox child,Rect attachRect,Color color,BoxConstraints constraints,Animation<double> scale}) : super(child){
  193. this._attachRect = attachRect;
  194. this._additionalConstraints = constraints;
  195. }
  196. @override
  197. void performLayout() {
  198. child.layout(_additionalConstraints.enforce(constraints), parentUsesSize: true);
  199. size = Size(constraints.maxWidth,constraints.maxHeight);
  200. final BoxParentData childParentData = child.parentData;
  201. childParentData.offset = calcOffset(child.size);
  202. }
  203. Offset calcOffset(Size size){
  204. double bodyLeft = 0.0;
  205. var isArrowUp = ScreenUtil.screenHeight > attachRect.bottom + size.height + CupertinoPopoverState._arrowHeight;
  206. if(attachRect.left > size.width / 2 &&
  207. ScreenUtil.screenWidth - attachRect.right > size.width / 2){ //判断是否可以在中间
  208. bodyLeft = attachRect.left + attachRect.width / 2 - size.width / 2;
  209. }else if(attachRect.left < size.width / 2){ //靠左
  210. bodyLeft = 10.0;
  211. }else{ //靠右
  212. bodyLeft = ScreenUtil.screenWidth - 10.0 - size.width;
  213. }
  214. if(isArrowUp){
  215. return Offset(bodyLeft,attachRect.bottom);
  216. }else{
  217. return Offset(bodyLeft,attachRect.top - size.height - CupertinoPopoverState._arrowHeight);
  218. }
  219. }
  220. @override
  221. void debugFillProperties(DiagnosticPropertiesBuilder properties) {
  222. super.debugFillProperties(properties);
  223. properties.add(DiagnosticsProperty<BoxConstraints>('additionalConstraints', additionalConstraints));
  224. }
  225. }
  226. class _CupertionPopoverContext extends SingleChildRenderObjectWidget{
  227. final Rect attachRect;
  228. final Color color;
  229. final Animation<double> scale;
  230. final double radius;
  231. _CupertionPopoverContext({Widget child,this.attachRect,this.color,this.scale,this.radius}):super(child:child);
  232. @override
  233. RenderObject createRenderObject(BuildContext context) => _CupertionPopoverContextRenderObject(
  234. attachRect: attachRect,
  235. color: color,
  236. scale: scale,
  237. radius: radius
  238. );
  239. @override
  240. void updateRenderObject(BuildContext context, _CupertionPopoverContextRenderObject renderObject) {
  241. renderObject
  242. ..attachRect = attachRect
  243. ..color = color
  244. ..scale = scale
  245. ..radius = radius;
  246. }
  247. }
  248. class _CupertionPopoverContextRenderObject extends RenderShiftedBox{
  249. Rect get attachRect => _attachRect;
  250. Rect _attachRect;
  251. set attachRect(Rect value) {
  252. if (_attachRect == value)
  253. return;
  254. _attachRect = value;
  255. markNeedsLayout();
  256. }
  257. Color get color => _color;
  258. Color _color;
  259. set color(Color value) {
  260. if (_color == value)
  261. return;
  262. _color = value;
  263. markNeedsLayout();
  264. }
  265. Animation<double> get scale => _scale;
  266. Animation<double> _scale;
  267. set scale(Animation<double> value) {
  268. if (_scale == value)
  269. return;
  270. _scale = value;
  271. markNeedsLayout();
  272. }
  273. double get radius => _radius;
  274. double _radius;
  275. set radius(double value) {
  276. if (_radius == value)
  277. return;
  278. _radius = value;
  279. markNeedsLayout();
  280. }
  281. _CupertionPopoverContextRenderObject({RenderBox child,Rect attachRect,Color color,Animation<double> scale,double radius}) : super(child){
  282. this._attachRect = attachRect;
  283. this._color = color;
  284. this._scale = scale;
  285. this._radius = radius;
  286. }
  287. @override
  288. void performLayout() {
  289. assert(constraints.maxHeight.isFinite);
  290. BoxConstraints childConstraints = BoxConstraints(maxHeight: constraints.maxHeight - CupertinoPopoverState._arrowHeight).enforce(constraints);
  291. child.layout(childConstraints, parentUsesSize: true);
  292. size = Size(child.size.width,child.size.height + CupertinoPopoverState._arrowHeight);
  293. final BoxParentData childParentData = child.parentData;
  294. var isArrowUp = ScreenUtil.screenHeight > attachRect.bottom + size.height + CupertinoPopoverState._arrowHeight;
  295. if(isArrowUp)
  296. {
  297. childParentData.offset = Offset(0.0, CupertinoPopoverState._arrowHeight);
  298. }
  299. }
  300. @override
  301. void paint(PaintingContext context, Offset offset) {
  302. // TODO: implement paint
  303. Matrix4 transform = Matrix4.identity();
  304. //
  305. var isArrowUp = ScreenUtil.screenHeight > attachRect.bottom + size.height + CupertinoPopoverState._arrowHeight;
  306. var arrowLeft =attachRect.left + attachRect.width / 2 - CupertinoPopoverState._arrowWidth / 2 - offset.dx;
  307. var translation = Offset(arrowLeft + CupertinoPopoverState._arrowWidth / 2,isArrowUp?0.0:size.height);
  308. transform.translate(translation.dx, translation.dy);
  309. transform.scale(scale.value, scale.value, 1.0);
  310. transform.translate(-translation.dx, -translation.dy);
  311. Rect arrowRect = Rect.fromLTWH(
  312. arrowLeft,
  313. isArrowUp?0.0:child.size.height,
  314. CupertinoPopoverState._arrowWidth,
  315. CupertinoPopoverState._arrowHeight);
  316. Rect bodyRect = Offset(0.0, isArrowUp?CupertinoPopoverState._arrowHeight:0.0) & child.size;
  317. context.pushClipPath(needsCompositing,
  318. offset,offset & size,
  319. getClip(size,isArrowUp,arrowRect,bodyRect),(context,offset){
  320. context.pushTransform(needsCompositing, offset, transform,(context,offset){
  321. final Paint backgroundPaint = Paint();
  322. backgroundPaint.color = color;
  323. context.canvas.drawRect(offset & size, backgroundPaint);
  324. super.paint(context,offset);
  325. });
  326. });
  327. }
  328. Path getClip(Size size,bool isArrowUp,Rect arrowRect,Rect bodyRect) {
  329. Path path = new Path();
  330. if(isArrowUp)
  331. {
  332. path.moveTo(arrowRect.left,arrowRect.bottom); //箭头
  333. path.lineTo(arrowRect.left + arrowRect.width / 2, arrowRect.top);
  334. path.lineTo(arrowRect.right, arrowRect.bottom);
  335. path.lineTo(bodyRect.right - radius,bodyRect.top); //右上角
  336. path.conicTo(bodyRect.right,bodyRect.top
  337. ,bodyRect.right,bodyRect.top + radius,1.0);
  338. path.lineTo(bodyRect.right,bodyRect.bottom - radius); //右下角
  339. path.conicTo(bodyRect.right,bodyRect.bottom
  340. ,bodyRect.right -radius ,bodyRect.bottom,1.0);
  341. path.lineTo(bodyRect.left + radius, bodyRect.bottom); //左下角
  342. path.conicTo(bodyRect.left,bodyRect.bottom
  343. ,bodyRect.left ,bodyRect.bottom - radius,1.0);
  344. path.lineTo(bodyRect.left, bodyRect.top + radius); //左上角
  345. path.conicTo(bodyRect.left,bodyRect.top
  346. ,bodyRect.left + radius,bodyRect.top,1.0);
  347. }else{
  348. path.moveTo(bodyRect.left + radius,bodyRect.top);
  349. path.lineTo(bodyRect.right - radius,bodyRect.top); //右上角
  350. path.conicTo(bodyRect.right,bodyRect.top
  351. ,bodyRect.right,bodyRect.top + radius,1.0);
  352. path.lineTo(bodyRect.right,bodyRect.bottom - radius); //右下角
  353. path.conicTo(bodyRect.right,bodyRect.bottom
  354. ,bodyRect.right -radius ,bodyRect.bottom,1.0);
  355. path.lineTo(arrowRect.right, arrowRect.top); //箭头
  356. path.lineTo(arrowRect.left + arrowRect.width / 2, arrowRect.bottom);
  357. path.lineTo(arrowRect.left,arrowRect.top);
  358. path.lineTo(bodyRect.left + radius, bodyRect.bottom); //左下角
  359. path.conicTo(bodyRect.left,bodyRect.bottom
  360. ,bodyRect.left ,bodyRect.bottom - radius,1.0);
  361. path.lineTo(bodyRect.left, bodyRect.top + radius); //左上角
  362. path.conicTo(bodyRect.left,bodyRect.top
  363. ,bodyRect.left + radius,bodyRect.top,1.0);
  364. }
  365. path.close();
  366. return path;
  367. }
  368. }