trapezoid.dart 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253
  1. import 'package:flutter/material.dart';
  2. import 'package:flutter/rendering.dart';
  3. import 'dart:math' show max;
  4. class Quadrilateral {
  5. final Offset a;
  6. final Offset b;
  7. final Offset c;
  8. final Offset d;
  9. Quadrilateral({
  10. required this.a,
  11. required this.b,
  12. required this.c,
  13. required this.d,
  14. });
  15. bool contains(Offset point) {
  16. final double px = point.dx;
  17. final double py = point.dy;
  18. final double cp1 =
  19. (b.dx - a.dx) * (py - a.dy) - (b.dy - a.dy) * (px - a.dx);
  20. final double cp2 =
  21. (c.dx - b.dx) * (py - b.dy) - (c.dy - b.dy) * (px - b.dx);
  22. final double cp3 =
  23. (d.dx - c.dx) * (py - c.dy) - (d.dy - c.dy) * (px - c.dx);
  24. final double cp4 =
  25. (a.dx - d.dx) * (py - d.dy) - (a.dy - d.dy) * (px - d.dx);
  26. if ((cp1 > 0 && cp2 > 0 && cp3 > 0 && cp4 > 0) ||
  27. (cp1 < 0 && cp2 < 0 && cp3 < 0 && cp4 < 0)) {
  28. return true;
  29. }
  30. return false;
  31. }
  32. }
  33. class TrapezoidBox extends MultiChildRenderObjectWidget {
  34. final EdgeInsets padding;
  35. final double mainAxisSpacing;
  36. final Color? background;
  37. TrapezoidBox({
  38. Key? key,
  39. required List<Widget> children,
  40. this.padding = EdgeInsets.zero,
  41. this.mainAxisSpacing = 0,
  42. this.background,
  43. }) : super(key: key, children: children);
  44. @override
  45. _RenderTrapezoidBox createRenderObject(BuildContext context) {
  46. return _RenderTrapezoidBox(padding, mainAxisSpacing, background);
  47. }
  48. @override
  49. void updateRenderObject(
  50. BuildContext context, covariant _RenderTrapezoidBox renderObject) {
  51. renderObject
  52. ..padding = padding
  53. ..mainAxisSpacing = mainAxisSpacing
  54. ..background = background;
  55. }
  56. }
  57. class TrapezoidParentData extends ContainerBoxParentData<RenderBox> {
  58. Offset a = Offset.zero;
  59. Offset b = Offset.zero;
  60. Offset c = Offset.zero;
  61. Offset d = Offset.zero;
  62. }
  63. class _RenderTrapezoidBox extends RenderBox
  64. with
  65. ContainerRenderObjectMixin<RenderBox, TrapezoidParentData>,
  66. RenderBoxContainerDefaultsMixin<RenderBox, TrapezoidParentData> {
  67. _RenderTrapezoidBox(
  68. this._padding,
  69. this._mainAxisSpacing,
  70. this._background,
  71. );
  72. EdgeInsets _padding;
  73. set padding(EdgeInsets value) {
  74. if (value != _padding) {
  75. _padding = value;
  76. markNeedsLayout();
  77. }
  78. }
  79. double _mainAxisSpacing;
  80. set mainAxisSpacing(double value) {
  81. if (value != _mainAxisSpacing) {
  82. _mainAxisSpacing = value;
  83. markNeedsLayout();
  84. }
  85. }
  86. Color? _background;
  87. set background(Color? value) {
  88. if (value != _background) {
  89. _background = value;
  90. markNeedsPaint();
  91. }
  92. }
  93. @override
  94. void setupParentData(covariant RenderObject child) {
  95. if (child.parentData is! TrapezoidParentData) {
  96. child.parentData = TrapezoidParentData();
  97. }
  98. }
  99. @override
  100. void performLayout() {
  101. var child = firstChild;
  102. final childConstraints = constraints.deflate(_padding);
  103. while (child != null) {
  104. assert(child is RenderBox);
  105. child.layout(childConstraints, parentUsesSize: true);
  106. child = childAfter(child);
  107. }
  108. child = firstChild;
  109. final double left = _padding.left;
  110. final double right = constraints.maxWidth - _padding.right;
  111. double start = _padding.top;
  112. int count = 0;
  113. while (child != null) {
  114. assert(child is RenderBox);
  115. count++;
  116. final parentData = child.parentData as TrapezoidParentData;
  117. final childSize = child.size;
  118. var end = start + childSize.height;
  119. if (count % 2 == 0) {
  120. parentData.a = Offset(left, start);
  121. parentData.b = Offset(right, start + 30);
  122. parentData.c = Offset(left, end);
  123. parentData.d = Offset(right, end - 30);
  124. parentData.offset = Offset(left, start);
  125. } else {
  126. parentData.a = Offset(left, start + 30);
  127. parentData.b = Offset(right, start);
  128. parentData.c = Offset(left, end - 30);
  129. parentData.d = Offset(right, end);
  130. parentData.offset = Offset(left, start);
  131. }
  132. start = end - 30 + _mainAxisSpacing;
  133. child = childAfter(child);
  134. }
  135. double height = _padding.top;
  136. child = firstChild;
  137. while (child != null) {
  138. height += child.size.height + _mainAxisSpacing;
  139. child = childAfter(child);
  140. }
  141. height += _padding.bottom - _mainAxisSpacing;
  142. height -= (childCount - 1) * 30;
  143. size = Size(
  144. constraints.biggest.width,
  145. height,
  146. );
  147. }
  148. @override
  149. bool hitTestChildren(BoxHitTestResult result, {required Offset position}) {
  150. RenderBox? child = firstChild;
  151. while (child != null) {
  152. final TrapezoidParentData childParentData =
  153. child.parentData! as TrapezoidParentData;
  154. final Quadrilateral quadrilateral = Quadrilateral(
  155. a: childParentData.a,
  156. b: childParentData.b,
  157. c: childParentData.d,
  158. d: childParentData.c,
  159. );
  160. bool isIn = quadrilateral.contains(position);
  161. if (isIn) {
  162. final bool isHit = result.addWithPaintOffset(
  163. offset: childParentData.offset,
  164. position: position,
  165. hitTest: (BoxHitTestResult result, Offset? transformed) {
  166. assert(transformed == position - childParentData.offset);
  167. return child!.hitTest(result, position: transformed!);
  168. },
  169. );
  170. if (isHit) {
  171. return true;
  172. }
  173. }
  174. child = childParentData.nextSibling;
  175. }
  176. return false;
  177. }
  178. @override
  179. void paint(PaintingContext context, Offset offset) {
  180. context.canvas.save();
  181. context.canvas.translate(offset.dx, offset.dy);
  182. _paintBackground(context.canvas);
  183. for (var child in getChildrenAsList()) {
  184. _paintChild(context, child);
  185. }
  186. context.canvas.restore();
  187. }
  188. void _paintBackground(Canvas canvas) {
  189. if(_background != null) {
  190. Path path = Path();
  191. path.moveTo(0, 30);
  192. path.lineTo(size.width, 0);
  193. path.lineTo(size.width, size.height);
  194. path.lineTo(0, size.height);
  195. path.close();
  196. canvas.save();
  197. canvas.clipPath(path);
  198. canvas.drawRect(Offset.zero&size, Paint()..color = _background!);
  199. canvas.restore();
  200. }
  201. }
  202. void _paintChild(PaintingContext context, RenderBox child) {
  203. TrapezoidParentData parentData = child.parentData as TrapezoidParentData;
  204. context.canvas.save();
  205. Path path = Path();
  206. path.moveTo(parentData.a.dx, parentData.a.dy);
  207. path.lineTo(parentData.b.dx, parentData.b.dy);
  208. path.lineTo(parentData.d.dx, parentData.d.dy);
  209. path.lineTo(parentData.c.dx, parentData.c.dy);
  210. path.close();
  211. context.canvas.clipPath(path);
  212. context.paintChild(child, parentData.offset);
  213. context.canvas.restore();
  214. }
  215. }