星级评选,星级,星级排行
整一个豆瓣的星级评选,效果图和原图如下
一、开发步骤
首先,明确一下如何来设计这个东西。
评分系统,一看图上就有两种不同的星星,一个表层的带颜色的星星,一个底层的灰色星星。
底层的灰色星星,都是满星,而表层的带颜色的星星,有半颗的。
这时候我们就知道了,要做一个层叠在一起的两组星星,一组底层的灰色满的,一组表层的可能不是满的。
1.创建一个底层用的空的星星,或者灰色的星星 (☆)
2.创建一排底层用的星星(☆☆☆☆☆)
3.创建一个表层用的满的带颜色的星星(★)
4.创建半个表层用的带颜色的星星(★半颗)
5.将他们叠在一起
二、开始开发
现在开始开发
- 创建一个底层用的空的星星,或者灰色的星星 (☆)
Icon unSelectedStar = Icon(Icons.star_border, color: Colors.red, size: 30);
- 创建一排 5个 底层用的星星(☆☆☆☆☆)
List.generate(5, (index) {
return Icon(Icons.star_border, color: Colors.red, size: 30);
})
- 创建一个表层用的满的带颜色的星星(★)
Icon(Icons.star, color: Colors.red, size: 30);
- 创建半个表层用的带颜色的星星(★半颗)
半颗星星需要用到剪切,剪切这里,我们需要用到一个东西ClipRect
,
ClipRect(
clipper: StarCustomClipper(leftWidth),
child: star,
);
clipper
我们看到要传一个 CustomClipper
CustomClipper
又是一个抽象类,所以我们看看实现过他的子类都是什么东西。一看就发现,有两个是锁着的(🔐)不让我们用。那我们看一下那个没有上锁的,看字面意思,我们猜测他是根据形状来剪切。但是我们需要根据分数的不同直接吃掉右边的,相当于用一个矩形框盖住他了。那我们干脆来自己实现一个 CustomClipper
class StarCustomClipper extends CustomClipper<Rect>{
double width;
StarCustomClipper(this.width);
@override
Rect getClip(Size size) {
// TODO: implement getClip
return Rect.fromLTRB(0, 0, width, size.height);
}
@override
bool shouldReclip(StarCustomClipper oldClipper) {
// TODO: implement shouldReclip
return oldClipper.width != this.width;
}
}
在 CustomClipper
子类中,我们有两个必须要实现的方法getClip
和shouldReclip
。
getClip
是用来告诉我们到底要怎么切,shouldReclip
是告诉我们要不要切。
这里我们通过传入切割的位置 Rect.fromLTRB(0, 0, width, size.height)
,来进行切割。这里的width
就是我们切下这一半来保留。
这时候我们就可以得到半颗星了。
- 计算一下要排列多少个满的星星和半个的星星
- 准备工作就做完了,这时候就把他们拼起来
1.首先我们整个方法根据数量来创建☆
//创建没有选中的星星
List <Widget> buildUnseletedStar() {
return List.generate(widget.maxStarCount, (index) {
return unSelectedStar;
});
}
2.然后我们整个方法根据数量来创建★
List <Widget> buildSeletedStar() {
List <Widget> starList = [];
///每颗星星多少分
double oneStarScore = widget.maxRate / widget.maxStarCount;
//判断几个星星
double allCount = widget.currentRate / oneStarScore;
//有没有半颗的
//判断整颗
int fulStarCount = allCount.floor();
//判断半颗
double partStarCount = allCount - fulStarCount;
//有没有多的
if (allCount >= widget.maxStarCount) {
allCount = widget.maxStarCount;
partStarCount = 0;
}
//添加整颗星星
starList.addAll(List.generate(fulStarCount, (index){ return widget.seletedStar;}));
//添加半颗星星
if (partStarCount > 0) {
//ClipRect 剪切
starList.add(ClipRect(
child: widget.seletedStar,
clipper: LYStarClipper(partStarCount * widget.starSize),
)
);
}
return starList;
}
}
3.最后我们把他们拼在一起
Stack(
children: [
Row(mainAxisSize: MainAxisSize.min, children:buildUnseletedStar()),
Row(mainAxisSize: MainAxisSize.min, children:buildSeletedStar()),
],
);
三、总结
这个东西难点在怎么做半颗星星,和计算有多少个星星。同时为了保持代码的健壮性,我们要做到在传过来的分数比总分数要高的时候,要么提示开发者出错了,要么就让他显示最大数值。
四、完整代码
import 'package:flutter/material.dart';
class LYCustomWidgtStarRating extends StatefulWidget {
///当前分数
final currentRate;
///最大分数
final maxRate;
///最大星星数量
final maxStarCount;
///底部星星样式
final Widget unseletedStar;
///顶部星星样式
final Widget seletedStar;
///星星大小
final double starSize;
LYCustomWidgtStarRating({
@required this.currentRate,
this.maxRate = 10,
this.maxStarCount = 5,
this.starSize = 30,
unseletedStar,
seletedStar,
}): seletedStar = seletedStar ?? Icon(Icons.star,color: Colors.red,size: starSize,),
unseletedStar = unseletedStar ?? Icon(Icons.star_border,color: Colors.red,size: starSize,);
@override
_LYCustomWidgtStarRatingState createState() => _LYCustomWidgtStarRatingState();
}
class _LYCustomWidgtStarRatingState extends State<LYCustomWidgtStarRating> {
@override
Widget build(BuildContext context) {
return Stack(
children: [
Row(mainAxisSize: MainAxisSize.min, children:buildUnseletedStar()),
Row(mainAxisSize: MainAxisSize.min, children:buildSeletedStar()),
]
,
);
}
//创建没有选中的星星
List <Widget> buildUnseletedStar() {
return List.generate(widget.maxStarCount, (index) {
return widget.unseletedStar;
});
}
List <Widget> buildSeletedStar() {
List <Widget> starList = [];
///每颗星星多少分
double oneStarScore = widget.maxRate / widget.maxStarCount;
//判断几个星星
double allCount = widget.currentRate / oneStarScore;
//有没有半颗的
//判断整颗
int fulStarCount = allCount.floor();
//判断半颗
double partStarCount = allCount - fulStarCount;
//有没有多的
if (allCount >= widget.maxStarCount) {
allCount = widget.maxStarCount;
partStarCount = 0;
}
//添加整颗星星
starList.addAll(List.generate(fulStarCount, (index){ return widget.seletedStar;}));
//添加半颗星星
if (partStarCount > 0) {
//ClipRect 剪切
starList.add(ClipRect(
child: widget.seletedStar,
clipper: LYStarClipper(partStarCount * widget.starSize),
)
);
}
return starList;
}
}
class LYStarClipper extends CustomClipper<Rect> {
double width;
LYStarClipper(this.width);
@override
Rect getClip(Size size) {
// TODO: implement getClip
return Rect.fromLTRB(0, 0, width, size.height);
}
@override
bool shouldReclip(LYStarClipper oldClipper) {
// TODO: implement shouldReclip
return oldClipper.width != width;
}
}