ruler_widget.dart 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185
  1. import 'dart:math';
  2. import 'dart:ui';
  3. import 'dart:ui' as ui;
  4. import 'package:flutter/material.dart';
  5. import 'package:sport/widgets/circular_percent_indicator.dart';
  6. class RulerWidget extends StatefulWidget {
  7. final double initialIndex;
  8. RulerWidget({this.initialIndex = 60});
  9. @override
  10. State<StatefulWidget> createState() => _State();
  11. }
  12. class _State extends State<RulerWidget> with SingleTickerProviderStateMixin {
  13. double _x = 0;
  14. double _value = 0;
  15. AnimationController? _animationController;
  16. Animation? _animation;
  17. @override
  18. void initState() {
  19. super.initState();
  20. _value = widget.initialIndex;
  21. _animationController = AnimationController(vsync: this, duration: Duration(milliseconds: 250))
  22. ..addStatusListener((status) {
  23. if (status == AnimationStatus.completed) {
  24. _notifyValue();
  25. }
  26. });
  27. }
  28. _notifyValue() {
  29. RulerValue(max(0, _value)).dispatch(context);
  30. }
  31. @override
  32. void dispose() {
  33. _animationController?.dispose();
  34. super.dispose();
  35. }
  36. @override
  37. Widget build(BuildContext context) {
  38. return GestureDetector(
  39. onHorizontalDragStart: (detail) {
  40. _x = detail.globalPosition.dx;
  41. },
  42. onHorizontalDragEnd: (detail) {
  43. if(_animationController == null)
  44. return;
  45. if (_value < 0.0) {
  46. _animation = Tween(begin: _value, end: 0.0).animate(
  47. _animationController!,
  48. )..addListener(() {
  49. setState(() {
  50. _value = _animation?.value ?? 0;
  51. });
  52. });
  53. _animationController!.forward(from: .0);
  54. }
  55. if (_value != _value.truncateToDouble()) {
  56. var old = _value * 1.0;
  57. var diff = _value - double.parse(_value.toStringAsFixed(1));
  58. _animation = Tween(begin: diff, end: .0).animate(
  59. _animationController!,
  60. )..addListener(() {
  61. setState(() {
  62. _value = old - diff - _animation?.value;
  63. });
  64. });
  65. _animationController!.forward(from: .0);
  66. }
  67. },
  68. onHorizontalDragUpdate: (detail) {
  69. _value = _value - (detail.globalPosition.dx - _x) / 50;
  70. _value = max(10, min(_value, 200));
  71. _x = detail.globalPosition.dx;
  72. setState(() {
  73. _notifyValue();
  74. });
  75. },
  76. child: CustomPaint(
  77. painter: _Painter(scrollLen: _value),
  78. child: Container(
  79. width: double.infinity,
  80. height: 280.0,)
  81. ),
  82. );
  83. }
  84. }
  85. class RulerValue extends Notification {
  86. final double value;
  87. RulerValue(this.value);
  88. }
  89. class _Painter extends CustomPainter {
  90. _Painter({required this.scrollLen});
  91. final double scrollLen;
  92. static const double _bgWidth = 100;
  93. var _bg = Paint()
  94. ..style = PaintingStyle.stroke
  95. ..color = const Color(0xfff2f2f2)
  96. ..strokeWidth = _bgWidth
  97. ..isAntiAlias = true;
  98. var _line = Paint()
  99. ..color = const Color(0xff6d6d6d)
  100. ..strokeWidth = 1
  101. ..isAntiAlias = true;
  102. final ParagraphStyle _labelStyle = ParagraphStyle(
  103. textAlign: TextAlign.center,
  104. fontWeight: FontWeight.w600,
  105. fontSize: 24,
  106. );
  107. final Paint _current = Paint()
  108. ..color = const Color(0xffFFC400)
  109. ..isAntiAlias = true;
  110. @override
  111. void paint(Canvas canvas, Size size) {
  112. canvas.save();
  113. canvas.clipRect(Rect.fromLTWH(0, 0, size.width, size.height));
  114. var center = Offset(size.width / 2, size.width + 120);
  115. var cx = center.dx;
  116. var cy = center.dy;
  117. var radius = size.width;
  118. // radius = size.width / 2;
  119. canvas.drawArc(Rect.fromCircle(center: center, radius: radius), pi, pi, false, _bg);
  120. var dy = center.dy - radius - _bgWidth / 2;
  121. var unit = 2;
  122. double startAngle = scrollLen * 10 * unit;
  123. // startAngle = startAngle % 360;
  124. // print("scrollLen: $scrollLen, startAngle:$startAngle, toint: ${startAngle.floor()}, ${startAngle % 90}");
  125. // canvas.save();
  126. canvas.translate(cx, cy);
  127. canvas.rotate(radians(-startAngle - unit));
  128. canvas.translate(-cx, -cy);
  129. double angle = -startAngle;
  130. // print("angle: $angle");
  131. int index = 0;
  132. while (angle <= 90) {
  133. angle += unit;
  134. index++;
  135. canvas.translate(cx, cy);
  136. canvas.rotate(radians(unit));
  137. canvas.translate(-cx, -cy);
  138. if (angle < -90) continue;
  139. bool scale = (index - 1) % 10 == 0;
  140. // print("angle: $angle, scale: $scale == ${ angle % 20}");
  141. canvas.drawLine(Offset(cx, dy), Offset(cx, dy + (scale ? 38.0 : 20.0)), _line);
  142. if (scale) {
  143. ParagraphBuilder pb = ParagraphBuilder(_labelStyle)
  144. ..pushStyle(ui.TextStyle(color: const Color(0xff515151)))
  145. ..addText("${(angle + startAngle) ~/ 20}");
  146. ParagraphConstraints constraints = ParagraphConstraints(width: 50);
  147. Paragraph paragraph = pb.build()..layout(constraints);
  148. paragraph.computeLineMetrics().forEach((element) {
  149. canvas.drawParagraph(paragraph, Offset(cx - 25, dy + 40.0));
  150. });
  151. }
  152. }
  153. canvas.restore();
  154. canvas.drawPath(
  155. Path()
  156. ..moveTo(cx, dy + 8)
  157. ..lineTo(cx - 8, dy - 14)
  158. ..lineTo(cx + 8, dy - 14)
  159. ..close(),
  160. _current);
  161. }
  162. @override
  163. bool shouldRepaint(_Painter oldDelegate) => oldDelegate.scrollLen != scrollLen;
  164. }