rating_widget.dart 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208
  1. import 'package:flutter/material.dart';
  2. import 'package:widgets/my_load_image.dart';
  3. class RatingWidget extends StatefulWidget {
  4. /// 星星数量
  5. final int count;
  6. /// 最大评分值
  7. final double maxRating;
  8. /// 当前选中的评分值
  9. final double value;
  10. /// 没一颗星的大小
  11. final double size;
  12. /// 每个星之间的间距
  13. final double padding;
  14. /// 空星的图片资源
  15. final String nomalImage;
  16. /// 满星的图片资源
  17. final String selectImage;
  18. /// 是否可以操作选择
  19. final bool selectAble;
  20. /// 选中评分的回调
  21. final ValueChanged<String> onRatingUpdate;
  22. /// 是否只显示整数的分数
  23. final bool integerOnly;
  24. RatingWidget({
  25. required this.nomalImage,
  26. required this.selectImage,
  27. required this.onRatingUpdate,
  28. this.count = 5,
  29. this.value = 5.0,
  30. this.maxRating = 5.0,
  31. this.size = 20,
  32. this.padding = 0,
  33. this.selectAble = false,
  34. this.integerOnly = false,
  35. });
  36. @override
  37. _RatingWidgetState createState() => _RatingWidgetState();
  38. }
  39. class _RatingWidgetState extends State<RatingWidget> {
  40. late double value;
  41. @override
  42. Widget build(BuildContext context) {
  43. return Listener(
  44. child: buildRowRating(),
  45. onPointerDown: (PointerDownEvent event) {
  46. double x = event.localPosition.dx;
  47. if (x < 0) x = 0;
  48. pointValue(x);
  49. },
  50. onPointerMove: (PointerMoveEvent event) {
  51. double x = event.localPosition.dx;
  52. if (x < 0) x = 0;
  53. pointValue(x);
  54. },
  55. onPointerUp: (_) {},
  56. behavior: HitTestBehavior.deferToChild,
  57. );
  58. }
  59. pointValue(double dx) {
  60. if (!widget.selectAble) {
  61. return;
  62. }
  63. if (dx >= widget.size * widget.count + widget.padding * (widget.count - 1)) {
  64. value = widget.maxRating;
  65. } else {
  66. for (double i = 1; i < widget.count + 1; i++) {
  67. if (dx > widget.size * i + widget.padding * (i - 1) && dx < widget.size * i + widget.padding * i) {
  68. value = i * (widget.maxRating / widget.count);
  69. break;
  70. } else if (dx > widget.size * (i - 1) + widget.padding * (i - 1) && dx < widget.size * i + widget.padding * i) {
  71. value = (dx - widget.padding * (i - 1)) / (widget.size * widget.count) * widget.maxRating;
  72. break;
  73. }
  74. }
  75. }
  76. if (widget.integerOnly) {
  77. value = value.ceilToDouble(); // 直接向上取整
  78. }
  79. setState(() {
  80. widget.onRatingUpdate(value.toStringAsFixed(1));
  81. });
  82. }
  83. int fullStars() {
  84. if (value != null) {
  85. return (value / (widget.maxRating / widget.count)).ceil(); // 使用向上取整
  86. }
  87. return 0;
  88. }
  89. double star() {
  90. if (widget.integerOnly) {
  91. return 0;
  92. }
  93. if (value != null) {
  94. if (widget.count / fullStars() == widget.maxRating / value) {
  95. return 0;
  96. }
  97. return (value % (widget.maxRating / widget.count)) / (widget.maxRating / widget.count);
  98. }
  99. return 0;
  100. }
  101. List<Widget> buildRow() {
  102. int full = fullStars();
  103. List<Widget> children = [];
  104. for (int i = 0; i < full; i++) {
  105. children.add(MyAssetImage(
  106. widget.selectImage,
  107. width: widget.size,
  108. height: widget.size,
  109. ));
  110. if (i < widget.count - 1) {
  111. children.add(
  112. SizedBox(
  113. width: widget.padding,
  114. ),
  115. );
  116. }
  117. }
  118. if (full < widget.count) {
  119. children.add(ClipRect(
  120. clipper: SMClipper(rating: star() * widget.size),
  121. child: MyAssetImage(
  122. widget.selectImage,
  123. width: widget.size,
  124. height: widget.size,
  125. ),
  126. ));
  127. }
  128. return children;
  129. }
  130. List<Widget> buildNomalRow() {
  131. List<Widget> children = [];
  132. for (int i = 0; i < widget.count; i++) {
  133. children.add(MyAssetImage(
  134. widget.nomalImage,
  135. width: widget.size,
  136. height: widget.size,
  137. ));
  138. if (i < widget.count - 1) {
  139. children.add(SizedBox(
  140. width: widget.padding,
  141. ));
  142. }
  143. }
  144. return children;
  145. }
  146. Widget buildRowRating() {
  147. return Container(
  148. child: Stack(
  149. children: <Widget>[
  150. Row(
  151. children: buildNomalRow(),
  152. ),
  153. Row(
  154. children: buildRow(),
  155. )
  156. ],
  157. ),
  158. );
  159. }
  160. @override
  161. void initState() {
  162. super.initState();
  163. value = widget.value;
  164. }
  165. }
  166. class SMClipper extends CustomClipper<Rect> {
  167. final double rating;
  168. SMClipper({required this.rating});
  169. @override
  170. Rect getClip(Size size) {
  171. return Rect.fromLTRB(0.0, 0.0, rating, size.height);
  172. }
  173. @override
  174. bool shouldReclip(SMClipper oldClipper) {
  175. return rating != oldClipper.rating;
  176. }
  177. }