如何正确创建评分星条?
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);
}
我正在尝试为用户评分创建一个星条,下图来自 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);
}