import 'package:flutter/material.dart'; import 'package:widgets/my_load_image.dart'; class RatingWidget extends StatefulWidget { final int count; final double maxRating; final double value; final double size; final double padding; final String nomalImage; final String selectImage; final bool selectAble; final ValueChanged onRatingUpdate; final bool integerOnly; RatingWidget({ required this.nomalImage, required this.selectImage, required this.onRatingUpdate, this.count = 5, this.value = 5.0, this.maxRating = 5.0, this.size = 20, this.padding = 0, this.selectAble = false, this.integerOnly = false, }); @override _RatingWidgetState createState() => _RatingWidgetState(); } class _RatingWidgetState extends State { late double value; @override void initState() { super.initState(); value = widget.value; } @override Widget build(BuildContext context) { return GestureDetector( onTapDown: widget.selectAble ? _handleTap : null, onHorizontalDragUpdate: widget.selectAble ? _handleDrag : null, child: buildRowRating(), ); } void _handleTap(TapDownDetails details) { _updateValue(details.localPosition.dx); } void _handleDrag(DragUpdateDetails details) { _updateValue(details.localPosition.dx); } void _updateValue(double dx) { if (dx < 0) dx = 0; if (dx >= widget.size * widget.count + widget.padding * (widget.count - 1)) { value = widget.maxRating; } else { for (int i = 1; i <= widget.count; i++) { if (dx < i * (widget.size + widget.padding)) { value = i * (widget.maxRating / widget.count); if (dx < (i - 1) * (widget.size + widget.padding) + widget.size) { value -= (widget.size - (dx - (i - 1) * (widget.size + widget.padding))) / widget.size * (widget.maxRating / widget.count); } break; } } } if (widget.integerOnly) { value = value.ceilToDouble(); } setState(() { widget.onRatingUpdate(value.toStringAsFixed(1)); }); } int fullStars() { return (value / (widget.maxRating / widget.count)).floor(); } double star() { if (widget.integerOnly) { return 0; } return (value % (widget.maxRating / widget.count)) / (widget.maxRating / widget.count) * widget.size; } List buildRow() { int full = fullStars(); List children = []; for (int i = 0; i < full; i++) { children.add(MyAssetImage( widget.selectImage, width: widget.size, height: widget.size, )); if (i < widget.count - 1) { children.add(SizedBox(width: widget.padding)); } } if (full < widget.count) { children.add( Stack( children: [ MyAssetImage( widget.nomalImage, width: widget.size, height: widget.size, ), ClipRect( clipper: SMClipper(rating: star()), child: MyAssetImage( widget.selectImage, width: widget.size, height: widget.size, ), ), ], ), ); } return children; } List buildNomalRow() { List children = []; for (int i = 0; i < widget.count; i++) { children.add(MyAssetImage( widget.nomalImage, width: widget.size, height: widget.size, )); if (i < widget.count - 1) { children.add(SizedBox(width: widget.padding)); } } return children; } Widget buildRowRating() { return Stack( children: [ Row(children: buildNomalRow()), Row(children: buildRow()), ], ); } } class SMClipper extends CustomClipper { final double rating; SMClipper({required this.rating}); @override Rect getClip(Size size) { return Rect.fromLTRB(0.0, 0.0, rating, size.height); } @override bool shouldReclip(SMClipper oldClipper) { return rating != oldClipper.rating; } }