dotted_line.dart 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213
  1. library dotted_line;
  2. import 'package:flutter/material.dart';
  3. /// Draw a dotted line.
  4. ///
  5. /// Basic line settings
  6. /// * [direction]
  7. /// * [lineLength]
  8. /// * [lineThickness]
  9. /// Dash settings
  10. /// * [dashLength]
  11. /// * [dashColor]
  12. /// * [dashGradient]
  13. /// * [dashRadius]
  14. /// Dash gap settings
  15. /// * [dashGapLength]
  16. /// * [dashGapColor]
  17. /// * [dashGapRadius]
  18. /// * [dashGapGradient]
  19. class DottedLine extends StatelessWidget {
  20. /// Creates dotted line with the given parameters
  21. const DottedLine({
  22. Key? key,
  23. this.direction = Axis.horizontal,
  24. this.lineLength = double.infinity,
  25. this.lineThickness = 1.0,
  26. this.dashLength = 4.0,
  27. this.dashColor = Colors.black,
  28. this.dashGradient,
  29. this.dashGapLength = 4.0,
  30. this.dashGapColor = Colors.transparent,
  31. this.dashGapGradient,
  32. this.dashRadius = 0.0,
  33. this.dashGapRadius = 0.0,
  34. }) : assert(
  35. dashGradient == null || dashGradient.length == 2,
  36. 'The dashGradient must have only two colors.\n'
  37. 'The beginning color and the ending color of the gradient.'),
  38. assert(
  39. dashGapGradient == null || dashGapGradient.length == 2,
  40. 'The dashGapGradient must have only two colors.\n'
  41. 'The beginning color and the ending color of the gradient.'),
  42. super(key: key);
  43. /// The direction of the entire dotted line. Default [Axis.horizontal].
  44. final Axis direction;
  45. /// The length of the entire dotted line. Default [double.infinity].
  46. final double lineLength;
  47. /// The thickness of the entire dotted line. Default (1.0).
  48. final double lineThickness;
  49. /// The length of the dash. Default (4.0).
  50. final double dashLength;
  51. /// The color of the dash. Default [Colors.black].
  52. ///
  53. /// This is ignored if [dashGradient] is non-null.
  54. final Color dashColor;
  55. /// The gradient colors of the dash. Default null.
  56. /// The first color is beginning color, the second one is ending color.
  57. ///
  58. /// If this is specified, [dashColor] has no effect.
  59. final List<Color>? dashGradient;
  60. /// The radius of the dash. Default (0.0).
  61. final double dashRadius;
  62. /// The length of the dash gap. Default (4.0).
  63. final double dashGapLength;
  64. /// The color of the dash gap. Default [Colors.transparent].
  65. ///
  66. /// This is ignored if [dashGapGradient] is non-null.
  67. final Color dashGapColor;
  68. /// The gradient colors of the dash gap. Default null.
  69. /// The first color is beginning color, the second one is ending color.
  70. ///
  71. /// If this is specified, [dashGapColor] has no effect.
  72. final List<Color>? dashGapGradient;
  73. /// The radius of the dash gap. Default (0.0).
  74. final double dashGapRadius;
  75. @override
  76. Widget build(BuildContext context) {
  77. final isHorizontal = direction == Axis.horizontal;
  78. return SizedBox(
  79. width: isHorizontal ? lineLength : lineThickness,
  80. height: isHorizontal ? lineThickness : lineLength,
  81. child: LayoutBuilder(builder: (context, constraints) {
  82. final lineLength = _getLineLength(constraints, isHorizontal);
  83. final dashAndDashGapCount = _calculateDashAndDashGapCount(lineLength);
  84. final dashCount = dashAndDashGapCount[0];
  85. final dashGapCount = dashAndDashGapCount[1];
  86. return Wrap(
  87. direction: direction,
  88. children: List.generate(dashCount + dashGapCount, (index) {
  89. if (index % 2 == 0) {
  90. final dashColor = _getDashColor(dashCount, index ~/ 2);
  91. final dash = _buildDash(isHorizontal, dashColor);
  92. return dash;
  93. } else {
  94. final dashGapColor = _getDashGapColor(dashGapCount, index ~/ 2);
  95. final dashGap = _buildDashGap(isHorizontal, dashGapColor);
  96. return dashGap;
  97. }
  98. }).toList(growable: false),
  99. );
  100. }),
  101. );
  102. }
  103. /// If [lineLength] is [double.infinity],
  104. /// get the maximum value of the parent widget.
  105. /// And if the value is specified, use the specified value.
  106. double _getLineLength(BoxConstraints constraints, bool isHorizontal) {
  107. return lineLength == double.infinity
  108. ? isHorizontal
  109. ? constraints.maxWidth
  110. : constraints.maxHeight
  111. : lineLength;
  112. }
  113. /// Calculate the count of (dash + dashGap).
  114. ///
  115. /// example1) [lineLength] is 10, [dashLength] is 1, [dashGapLength] is 1.
  116. /// "- - - - - "
  117. /// example2) [lineLength] is 10, [dashLength] is 1, [dashGapLength] is 2.
  118. /// "- - - -"
  119. List<int> _calculateDashAndDashGapCount(double lineLength) {
  120. var dashAndDashGapLength = dashLength + dashGapLength;
  121. var dashCount = lineLength ~/ dashAndDashGapLength;
  122. var dashGapCount = lineLength ~/ dashAndDashGapLength;
  123. if (dashLength <= lineLength % dashAndDashGapLength) {
  124. dashCount += 1;
  125. }
  126. return [dashCount, dashGapCount];
  127. }
  128. Widget _buildDash(bool isHorizontal, Color color) {
  129. return Container(
  130. decoration: BoxDecoration(
  131. color: color,
  132. borderRadius: BorderRadius.circular(dashRadius),
  133. ),
  134. width: isHorizontal ? dashLength : lineThickness,
  135. height: isHorizontal ? lineThickness : dashLength,
  136. );
  137. }
  138. Color _getDashColor(int maxDashCount, int index) {
  139. return dashGradient == null
  140. ? dashColor
  141. : _calculateGradientColor(
  142. dashGradient![0],
  143. dashGradient![1],
  144. maxDashCount,
  145. index,
  146. );
  147. }
  148. Widget _buildDashGap(bool isHorizontal, Color color) {
  149. return Container(
  150. decoration: BoxDecoration(
  151. color: color,
  152. borderRadius: BorderRadius.circular(dashGapRadius),
  153. ),
  154. width: isHorizontal ? dashGapLength : lineThickness,
  155. height: isHorizontal ? lineThickness : dashGapLength,
  156. );
  157. }
  158. Color _getDashGapColor(int maxDashGapCount, int index) {
  159. return dashGapGradient == null
  160. ? dashGapColor
  161. : _calculateGradientColor(
  162. dashGapGradient![0],
  163. dashGapGradient![1],
  164. maxDashGapCount,
  165. index,
  166. );
  167. }
  168. Color _calculateGradientColor(
  169. Color startColor,
  170. Color endColor,
  171. int maxItemCount,
  172. int index,
  173. ) {
  174. var diffAlpha = (endColor.alpha - startColor.alpha);
  175. var diffRed = (endColor.red - startColor.red);
  176. var diffGreen = (endColor.green - startColor.green);
  177. var diffBlue = (endColor.blue - startColor.blue);
  178. var amountOfChangeInAlphaPerItem = diffAlpha ~/ maxItemCount;
  179. var amountOfChangeInRedPerItem = diffRed ~/ maxItemCount;
  180. var amountOfChangeInGreenPerItem = diffGreen ~/ maxItemCount;
  181. var amountOfChangeInBluePerItem = diffBlue ~/ maxItemCount;
  182. return startColor
  183. .withAlpha(startColor.alpha + amountOfChangeInAlphaPerItem * index)
  184. .withRed(startColor.red + amountOfChangeInRedPerItem * index)
  185. .withGreen(startColor.green + amountOfChangeInGreenPerItem * index)
  186. .withBlue(startColor.blue + amountOfChangeInBluePerItem * index);
  187. }
  188. }