bubble_widget.dart 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312
  1. import 'dart:math';
  2. import 'package:flutter/material.dart';
  3. enum BubbleArrowDirection { top, bottom, right, left }
  4. // ignore: must_be_immutable
  5. class BubbleWidget extends StatelessWidget {
  6. // 尖角位置
  7. final position;
  8. // 尖角高度
  9. var arrHeight;
  10. // 尖角角度
  11. var arrAngle;
  12. // 圆角半径
  13. var radius;
  14. // 宽度
  15. final width;
  16. // 高度
  17. final height;
  18. // 边距
  19. double length;
  20. // 颜色
  21. Color color;
  22. // 边框颜色
  23. Color? borderColor;
  24. // 边框宽度
  25. final strokeWidth;
  26. // 填充样式
  27. final style;
  28. // 子 Widget
  29. final child;
  30. // 子 Widget 与起泡间距
  31. var innerPadding;
  32. BubbleWidget(
  33. this.width,
  34. this.height,
  35. this.color,
  36. this.position, {
  37. Key? key,
  38. this.length = -1.0,
  39. this.arrHeight = 12.0,
  40. this.arrAngle = 60.0,
  41. this.radius = 10.0,
  42. this.strokeWidth = 4.0,
  43. this.style = PaintingStyle.fill,
  44. this.borderColor,
  45. this.child,
  46. this.innerPadding = 6.0,
  47. }) : super(key: key);
  48. @override
  49. Widget build(BuildContext context) {
  50. if (style == PaintingStyle.stroke && borderColor == null) {
  51. borderColor = color;
  52. }
  53. if (arrAngle < 0.0 || arrAngle >= 180.0) {
  54. arrAngle = 60.0;
  55. }
  56. if (arrHeight < 0.0) {
  57. arrHeight = 0.0;
  58. }
  59. if (radius < 0.0 || radius > width * 0.5 || radius > height * 0.5) {
  60. radius = 0.0;
  61. }
  62. if (position == BubbleArrowDirection.top ||
  63. position == BubbleArrowDirection.bottom) {
  64. if (length < 0.0 || length >= width - 2 * radius) {
  65. length = width * 0.5 - arrHeight * tan(_angle(arrAngle * 0.5)) - radius;
  66. }
  67. } else {
  68. if (length < 0.0 || length >= height - 2 * radius) {
  69. length =
  70. height * 0.5 - arrHeight * tan(_angle(arrAngle * 0.5)) - radius;
  71. }
  72. }
  73. if (innerPadding < 0.0 ||
  74. innerPadding >= width * 0.5 ||
  75. innerPadding >= height * 0.5) {
  76. innerPadding = 2.0;
  77. }
  78. Widget bubbleWidget;
  79. if (style == PaintingStyle.fill) {
  80. bubbleWidget = Container(
  81. width: width,
  82. height: height,
  83. child: Stack(children: <Widget>[
  84. CustomPaint(
  85. painter: BubbleCanvas(context, width, height, color, position,
  86. arrHeight, arrAngle, radius, strokeWidth, style, length)),
  87. _paddingWidget()
  88. ]));
  89. } else {
  90. bubbleWidget = Container(
  91. width: width,
  92. height: height,
  93. child: Stack(children: <Widget>[
  94. CustomPaint(
  95. painter: BubbleCanvas(
  96. context,
  97. width,
  98. height,
  99. color,
  100. position,
  101. arrHeight,
  102. arrAngle,
  103. radius,
  104. strokeWidth,
  105. PaintingStyle.fill,
  106. length)),
  107. CustomPaint(
  108. painter: BubbleCanvas(
  109. context,
  110. width,
  111. height,
  112. borderColor,
  113. position,
  114. arrHeight,
  115. arrAngle,
  116. radius,
  117. strokeWidth,
  118. style,
  119. length)),
  120. _paddingWidget()
  121. ]));
  122. }
  123. return bubbleWidget;
  124. }
  125. Widget _paddingWidget() {
  126. return Padding(
  127. padding: EdgeInsets.only(
  128. top: (position == BubbleArrowDirection.top)
  129. ? arrHeight + innerPadding
  130. : innerPadding,
  131. right: (position == BubbleArrowDirection.right)
  132. ? arrHeight + innerPadding
  133. : innerPadding,
  134. bottom: (position == BubbleArrowDirection.bottom)
  135. ? arrHeight + innerPadding
  136. : innerPadding,
  137. left: (position == BubbleArrowDirection.left)
  138. ? arrHeight + innerPadding
  139. : innerPadding),
  140. child: Center(child: this.child));
  141. }
  142. }
  143. class BubbleCanvas extends CustomPainter {
  144. BuildContext context;
  145. final position;
  146. final arrHeight;
  147. final arrAngle;
  148. final radius;
  149. final width;
  150. final height;
  151. final length;
  152. final color;
  153. final strokeWidth;
  154. final style;
  155. BubbleCanvas(
  156. this.context,
  157. this.width,
  158. this.height,
  159. this.color,
  160. this.position,
  161. this.arrHeight,
  162. this.arrAngle,
  163. this.radius,
  164. this.strokeWidth,
  165. this.style,
  166. this.length);
  167. @override
  168. void paint(Canvas canvas, Size size) {
  169. Path path = Path();
  170. path.arcTo(
  171. Rect.fromCircle(
  172. center: Offset(
  173. (position == BubbleArrowDirection.left)
  174. ? radius + arrHeight
  175. : radius,
  176. (position == BubbleArrowDirection.top)
  177. ? radius + arrHeight
  178. : radius),
  179. radius: radius),
  180. pi,
  181. pi * 0.5,
  182. false);
  183. if (position == BubbleArrowDirection.top) {
  184. path.lineTo(length + radius, arrHeight);
  185. path.lineTo(
  186. length + radius + arrHeight * tan(_angle(arrAngle * 0.5)), 0.0);
  187. path.lineTo(length + radius + arrHeight * tan(_angle(arrAngle * 0.5)) * 2,
  188. arrHeight);
  189. }
  190. path.lineTo(
  191. (position == BubbleArrowDirection.right)
  192. ? width - radius - arrHeight
  193. : width - radius,
  194. (position == BubbleArrowDirection.top) ? arrHeight : 0.0);
  195. path.arcTo(
  196. Rect.fromCircle(
  197. center: Offset(
  198. (position == BubbleArrowDirection.right)
  199. ? width - radius - arrHeight
  200. : width - radius,
  201. (position == BubbleArrowDirection.top)
  202. ? radius + arrHeight
  203. : radius),
  204. radius: radius),
  205. -pi * 0.5,
  206. pi * 0.5,
  207. false);
  208. if (position == BubbleArrowDirection.right) {
  209. path.lineTo(width - arrHeight, length + radius);
  210. path.lineTo(
  211. width, length + radius + arrHeight * tan(_angle(arrAngle * 0.5)));
  212. path.lineTo(width - arrHeight,
  213. length + radius + arrHeight * tan(_angle(arrAngle * 0.5)) * 2);
  214. }
  215. path.lineTo(
  216. (position == BubbleArrowDirection.right) ? width - arrHeight : width,
  217. (position == BubbleArrowDirection.bottom)
  218. ? height - radius - arrHeight
  219. : height - radius);
  220. path.arcTo(
  221. Rect.fromCircle(
  222. center: Offset(
  223. (position == BubbleArrowDirection.right)
  224. ? width - radius - arrHeight
  225. : width - radius,
  226. (position == BubbleArrowDirection.bottom)
  227. ? height - radius - arrHeight
  228. : height - radius),
  229. radius: radius),
  230. pi * 0,
  231. pi * 0.5,
  232. false);
  233. if (position == BubbleArrowDirection.bottom) {
  234. path.lineTo(width - radius - length, height - arrHeight);
  235. path.lineTo(
  236. width - radius - length - arrHeight * tan(_angle(arrAngle * 0.5)),
  237. height);
  238. path.lineTo(
  239. width - radius - length - arrHeight * tan(_angle(arrAngle * 0.5)) * 2,
  240. height - arrHeight);
  241. }
  242. path.lineTo(
  243. (position == BubbleArrowDirection.left) ? radius + arrHeight : radius,
  244. (position == BubbleArrowDirection.bottom)
  245. ? height - arrHeight
  246. : height);
  247. path.arcTo(
  248. Rect.fromCircle(
  249. center: Offset(
  250. (position == BubbleArrowDirection.left)
  251. ? radius + arrHeight
  252. : radius,
  253. (position == BubbleArrowDirection.bottom)
  254. ? height - radius - arrHeight
  255. : height - radius),
  256. radius: radius),
  257. pi * 0.5,
  258. pi * 0.5,
  259. false);
  260. if (position == BubbleArrowDirection.left) {
  261. path.lineTo(arrHeight, height - radius - length);
  262. path.lineTo(0.0,
  263. height - radius - length - arrHeight * tan(_angle(arrAngle * 0.5)));
  264. path.lineTo(
  265. arrHeight,
  266. height -
  267. radius -
  268. length -
  269. arrHeight * tan(_angle(arrAngle * 0.5)) * 2);
  270. }
  271. path.lineTo((position == BubbleArrowDirection.left) ? arrHeight : 0.0,
  272. (position == BubbleArrowDirection.top) ? radius + arrHeight : radius);
  273. path.close();
  274. canvas.drawPath(
  275. path,
  276. Paint()
  277. ..color = color
  278. ..style = style
  279. ..strokeCap = StrokeCap.round
  280. ..strokeWidth = strokeWidth);
  281. }
  282. @override
  283. bool shouldRepaint(CustomPainter oldDelegate) {
  284. return true;
  285. }
  286. }
  287. double _angle(angle) {
  288. return angle * pi / 180;
  289. }