How to create rating star bar properly?

That happens because of the height specified for the row widget. The gestures are detected only to that height. Removing the height will scale the row to fit the children, thus allowing the tap gestures on the IconButton to be detected perfectly.

I think I made a slightly better solution.

Rating component code:

int _rating = 0;

void rate(int rating) {
  //Other actions based on rating such as api calls.
  setState(() {
    _rating = rating;
  });
}

@override
Widget build(BuildContext context) {
  return new Scaffold(
    appBar: new AppBar(
      title: new Text("My Rating"),
    ),
    body: new Center(
      child: new Container(
        width: 500.0,
        child: new Row(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            new GestureDetector(
              child: new Icon(
                Icons.star,
                color: _rating >= 1 ? Colors.orange : Colors.grey,
              ),
              onTap: () => rate(1),
            ),
            new GestureDetector(
              child: new Icon(
                Icons.star,
                color: _rating >= 2 ? Colors.orange : Colors.grey,
              ),
              onTap: () => rate(2),
            ),
            new GestureDetector(
              child: new Icon(
                Icons.star,
                color: _rating >= 3 ? Colors.orange : Colors.grey,
              ),
              onTap: () => rate(3),
            ),
            new GestureDetector(
              child: new Icon(
                Icons.star,
                color: _rating >= 4 ? Colors.orange : Colors.grey,
              ),
              onTap: () => rate(4),
            ),
            new GestureDetector(
              child: new Icon(
                Icons.star,
                color: _rating >= 5 ? Colors.orange : Colors.grey,
              ),
              onTap: () => rate(5),
            )
          ],
        ),
      ),
    ),
  );
}

If you wish to use you code in the question, then Replace:

child: new Container(
      height: 10.0,
      width: 500.0,
      child: new Row(
          mainAxisAlignment: MainAxisAlignment.center,

With:

child: new Container(
      width: 500.0,
      child: new Row(
          mainAxisAlignment: MainAxisAlignment.center,

Hope this helped!


Another great alternative is using this library: flutter_rating_bar. It is well implemented, elegant, and easily configurable.

https://pub.dev/packages/flutter_rating_bar

Example:

RatingBar(
   initialRating: 3,
   minRating: 1,
   direction: Axis.horizontal,
   allowHalfRating: true,
   itemCount: 5,
   itemPadding: EdgeInsets.symmetric(horizontal: 4.0),
   itemBuilder: (context, _) => Icon(
     Icons.star,
     color: Colors.amber,
   ),
   onRatingUpdate: (rating) {
     print(rating);
   },
);

Too much repetition and padding! duh

Anyway, that's how I'd do it. Simple, reusable. You can use it both with and without clicks (no click disable the ripple effect). Half stars too. And use primary color for filled stars if no color is specified.

enter image description here

typedef void RatingChangeCallback(double rating);

class StarRating extends StatelessWidget {
  final int starCount;
  final double rating;
  final RatingChangeCallback onRatingChanged;
  final Color color;

  StarRating({this.starCount = 5, this.rating = .0, this.onRatingChanged, this.color});

  Widget buildStar(BuildContext context, int index) {
    Icon icon;
    if (index >= rating) {
      icon = new Icon(
        Icons.star_border,
        color: Theme.of(context).buttonColor,
      );
    }
    else if (index > rating - 1 && index < rating) {
      icon = new Icon(
        Icons.star_half,
        color: color ?? Theme.of(context).primaryColor,
      );
    } else {
      icon = new Icon(
        Icons.star,
        color: color ?? Theme.of(context).primaryColor,
      );
    }
    return new InkResponse(
      onTap: onRatingChanged == null ? null : () => onRatingChanged(index + 1.0),
      child: icon,
    );
  }

  @override
  Widget build(BuildContext context) {
    return new Row(children: new List.generate(starCount, (index) => buildStar(context, index)));
  }
}

You can then use it like this :

class Test extends StatefulWidget {
  @override
  _TestState createState() => new _TestState();
}

class _TestState extends State<Test> {
  double rating = 3.5;

  @override
  Widget build(BuildContext context) {
    return new StarRating(
      rating: rating,
      onRatingChanged: (rating) => setState(() => this.rating = rating),
    );
  }
}

There is a package on pub.dev to do this easily.

Check this - https://pub.dev/packages/smooth_star_rating

Complete Example

 import 'package:flutter/material.dart';
 import 'package:smooth_star_rating/smooth_star_rating.dart';

 void main() => runApp(MyApp());

class MyApp extends StatefulWidget {
 @override
  _MyAppState createState() => _MyAppState();
}

 class _MyAppState extends State<MyApp> {
 var rating = 0.0;

 @override
 Widget build(BuildContext context) {
    return MaterialApp(
    title: 'Rating bar demo',
    theme: ThemeData(
    primarySwatch: Colors.green,
  ),
  debugShowCheckedModeBanner: false,
  home: Scaffold(
    body: Center(
        child: SmoothStarRating(
      rating: rating,
      size: 45,
      starCount: 5,
      onRatingChanged: (value) {
        setState(() {
          rating = value;
        });
      },
      )),
    ),
  );
 }
}

Demo

enter image description here

Tags:

Dart

Flutter