如何正确创建评分星条?

How to create rating star bar properly?

我正在尝试为用户评分创建一个星条,下图来自 play 商店以说明我正在尝试实现的目标:

我已经能够实现以下功能,正如您所看到的那样,但是当我查看我的代码时,我觉得一定有更聪明的方法来做到这一点,而真正有问题的部分是IconButton 命中区域向上移动了一点,因此当您触摸实际的星星时,它不会注册为触摸事件(即:您必须瞄准比按钮所在的位置更高的位置,以便您的触摸注册,这会导致糟糕的用户体验)你可以通过点击任何星星时留意飞溅效果来检查我的意思:

var _myColorOne = Colors.grey;
  var _myColorTwo = Colors.grey;
  var _myColorThree = Colors.grey;
  var _myColorFour = Colors.grey;
  var _myColorFive = Colors.grey;


  @override
  Widget build(BuildContext context) {
    return new Scaffold(
     appBar: new AppBar(
       title: new Text("My Rating"),
     ),
      body:  new Center(
        child: new Container(
          height: 10.0,
          width: 500.0,
          child: new Row(
              mainAxisAlignment: MainAxisAlignment.center,
              children: <Widget>[
                new IconButton(icon: new Icon(Icons.star),
                  onPressed: ()=>setState((){
                    _myColorOne=Colors.orange;
                    _myColorTwo=null;
                    _myColorThree=null;
                    _myColorFour=null;
                    _myColorFive=null;
                }),color: _myColorOne,),
                new IconButton(icon: new Icon(Icons.star),
                  onPressed: ()=>setState((){
                    _myColorOne=Colors.orange;
                    _myColorTwo=Colors.orange;
                    _myColorThree=Colors.grey;
                    _myColorFour=Colors.grey;
                    _myColorFive=Colors.grey;
                }),color: _myColorTwo,),
                new IconButton(icon: new Icon(Icons.star), onPressed: ()=>setState((){
                  _myColorOne=Colors.orange;
                  _myColorTwo=Colors.orange;
                  _myColorThree=Colors.orange;
                  _myColorFour=Colors.grey;
                  _myColorFive=Colors.grey;
                }),color: _myColorThree,),
                new IconButton(icon: new Icon(Icons.star), onPressed: ()=>setState((){
                  _myColorOne=Colors.orange;
                  _myColorTwo=Colors.orange;
                  _myColorThree=Colors.orange;
                  _myColorFour=Colors.orange;
                  _myColorFive=Colors.grey;
                }),color: _myColorFour,),
                new IconButton(icon: new Icon(Icons.star), onPressed: ()=>setState((){
                  _myColorOne=Colors.orange;
                  _myColorTwo=Colors.orange;
                  _myColorThree=Colors.orange;
                  _myColorFour=Colors.orange;
                  _myColorFive=Colors.orange;
                }),color: _myColorFive,),
              ],

          ),
        ),
      ),
    );
  }

那么这里更好的方法是什么?

这是因为为行小部件指定的高度。仅在该高度检测到手势。删除高度将缩放行以适合子项,从而允许完美检测 IconButton 上的点击手势。

我想我做了一个稍微好一点的解决方案。

评级组件代码:

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),
            )
          ],
        ),
      ),
    ),
  );
}

如果您想在问题中使用您的代码,那么 替换:

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

与:

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

希望对您有所帮助!

太多的重复和填充!呃

无论如何,我就是这样做的。简单,可重复使用。 您可以在点击和不点击的情况下使用它(没有点击会禁用涟漪效应)。 半星也行。如果未指定颜色,则填充星星使用原色。

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)));
  }
}

然后您可以像这样使用它:

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),
    );
  }
}

这是我的

List<bool> ratingStarStatus = <bool>[
true,
false,
false,
false,
false
];

int rating=1;

Widget addImage(String icon){
    return new Image.asset(
        icon,
        height: 25.0,
        width: 25.0,
    );
}

Widget addRatingImage(int index){
    return new InkWell(
        onTap: (){
            rating=index+1;
            reDrawRating(index);
        },
        child: addImage(ratingStarStatus[index]?Constant.iconStarEnable: Constant.iconStarDisable)
    );
}
void reDrawRating(int index){
    for(int i=0;i<5;i++){
        if(i<=index){
            setState(() {
                ratingStarStatus[i]=true;
            });
        }else{
            setState(() {
                ratingStarStatus[i]=false;
        });
        }
    }
}

Widget addRatingView(){
    var stars = <Widget>[];
    for (var i = 0; i < 5; i++) {
        var star = addRatingImage(i);
        var padding = new Padding(padding: const EdgeInsets.fromLTRB(0.0, 0.0, 12.0, 0.0));
        stars.add(star);
        stars.add(padding);
    }

    return new Row(
        crossAxisAlignment: CrossAxisAlignment.center,
        mainAxisAlignment: MainAxisAlignment.center,
        children: stars,
    );
}

调用addRatingView函数附加评分视图

如果有人还想显示文本,那么您可以使用这段代码。 该代码适用于双精度值或整数值,我将其与此处 smoothStarRating.

的 SmoothStarRating 小部件一起使用
Text(
    rate == 5.0
        ? 'Its 5'
        : ((rate < 5.0) & (rate > 3.0))
            ? 'Its 4'
            : ((rate < 4.0) & (rate > 2.0))
                ? 'Its 3'
                : ((rate < 3.0) & (rate > 1.0))
                    ? 'Its 2'
                    : ((rate < 2.0) & (rate > 0.0))
                        ? 'Its 1'
                        : 'Its0',
),

我尝试修改教程以创建交互式星级。这是我在不需要 InkWell.

的情况下使用循环和 onPressed 想到的
`class RatingsWidget extends StatefulWidget {
  @override
  _RatingsWidgetState createState() => _RatingsWidgetState();
}

class _RatingsWidgetState extends State<RatingsWidget> {
  int _rating = 0;

  @override
  Widget build(BuildContext context) {
    List<Widget> array = [];
    var filled = Colors.blue;
    var empty = Colors.black;
    for (var i = 1; i <= 5; i++) {
      array.add(IconButton(
        icon: Icon(Icons.star),
        color: (_rating < i ? empty : filled),
        onPressed: () {
          setState(() {
            _rating = i;
          });
        },
      ));
    }
    return Row(
      children: array,
      mainAxisAlignment: MainAxisAlignment.center,
    );
  }
}`

pub.dev 上有一个包可以轻松做到这一点。

检查这个 - https://pub.dev/packages/smooth_star_rating

完整示例

 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;
        });
      },
      )),
    ),
  );
 }
}

演示

另一个很好的选择是使用这个库:flutter_rating_bar。它实现良好、优雅且易于配置。

https://pub.dev/packages/flutter_rating_bar

示例:

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);
   },
);

A cool way of rating

class RatingButtons extends StatefulWidget {
  const RatingButtons({
    Key key,
    @required this.feedbackIcons,
  }) : super(key: key);

  final List<Widget> feedbackIcons;
//Here you can create a list of icons for rating or 
//you can pass the icon list while using this widget

  @override
  _RatingButtonsState createState() => _RatingButtonsState();
}

class _RatingButtonsState extends State<RatingButtons> {
  int rating;

  @override
  Widget build(BuildContext context) {
    return Row(mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [
      ...widget.feedbackIcons.map((icon) {
        return GestureDetector(
          child: rating == widget.feedbackIcons.indexOf(icon)
              ? CircleAvatar(
                  radius: 30,
                  backgroundColor: kThemeColor,
                  child: icon,
                )
              : icon,
          onTap: () {
            setState(() {
              rating = widget.feedbackIcons.indexOf(icon);
            });
          },
        );
      }).toList()
    ]);
  }
}

以下是我如何根据 Material icon without any other third party libraries.

制作 5 star rating 辅助小部件

注意:我确定评分小于 2 是不好的,所以我将此类星星的颜色设置为红色,否则,我将条形颜色设置为绿色。当然,您可以根据自己的需要更改颜色。

然后我将列表 [1,2,3,4,5] 映射到给定评级中的值的适当星形图标和 return Row or star ratings.

您可以快速轻松地将其转换为 StatelessWidget,但目前这不是我的首要任务。

Widget buildRatingStar(double starValue) {
  Color color = starValue < 2 ? Colors.red : Colors.green;
  var starIconsMap = [1, 2, 3, 4, 5].map((e) {
    if (starValue >= e) {
      return Icon(
        Icons.star_rate,
        color: color,
      );
    } else if (starValue < e && starValue > e - 1) {
      return Icon(
        Icons.star_half,
        color: color,
      );
    } else {
      return Icon(Icons.star_border, color: color);
    }
  }).toList();

  return Row(children: starIconsMap);
}