Flutter 绘制集录 | n 角星

一、组件使用

1、组件地址与功能简介

【pub地址 】 【github地址】

1dependencies:
2  flutter_star: $lastVersion

目标: 使用canvas手工打造,一个完美的星星评分组件。

1---->[StarScore 星星显示组件]----
2[1] 比如显示4.2: 会有5颗星, 前四颗填满,后一刻填充20%
3StarScore 为 Stateless组件,仅负责显示的需求
4
5---->[CustomRating 星星评分组件]----
6[2] 可指定最大值,也就是显示多少个星星
7[3] 点击时会改变状态,进行评分,支持半星评分
8[4] 支持评分回调
9
10---->[StarWidget组件]----
11[5]. 可定义星星的显示进度情况 0% ~ 100 % 无死角
12[6]. 可定义星星的角数
13[7]. 可定义星星的颜色、大小 

2 、StarScore 属性

分数展示组件

名称 类型 功能 备注 默认
score double 分数 0
star Star 第四点 星星属性配置 Star()
tail Widget 尾部的组件 null

1StarScore(
2  score: 4.8,
3  star: Star(
4      fillColor: Colors.tealAccent,
5      emptyColor: Colors.grey.withAlpha(88)),
6  tail: Column(
7    children: [
8      Text("综合评分"),
9      Text("4.8"),
10    ],
11  ),
12),

3 、CustomRating 属性

评分组件

名称 类型 功能 备注 默认
max int 最大星星数 5
score double 分数 0
star Star 第四点 星星属性配置 Star()
onRating Fluction(double) 点击回调 @required null

1.最简使用

1CustomRating(onRating: (s) {
2   print(s);
3 }),

2.可高度定制

1CustomRating(
2     max6,
3     score: 3.0,
4     star: Star(
5         num: 12,
6         fillColor: Colors.orangeAccent,
7         fat: 0.6,
8         emptyColor: Colors.grey.withAlpha(88)),
9    onRating: (s) {
10       print(s);
11     }),

4、Star 组件

星星组件 : 高度可定制的配置类

名称 类型 功能 备注 默认
progress double 填充的进度 [0,1] 0.0
num int 星星的角数 大于3 5
fat double 星星的胖瘦 (0,1] 0.5
emptyColor Color 星星的色 Colors.grey
fillColor Color 星星的填充色 Colors.yellow
size double 星星的大小 20

1. 进度填充:progress

2. 星星的角数:num

3. 星星的胖瘦:fat

4. 星星的颜色:fillColor和emptyColor

展示结束,下面进入正文

二、如何自定义绘制的组件

1.分析组件的需求,抽离出需要配置的属性

1class Star {
2  final int num;
3  final double progress;
4  final Color emptyColor;
5  final Color fillColor;
6  final double size;
7  final double fat;
8
9  const Star({this.progress = 0,
10    this.fat = 0.5,
11      this.fillColor = Colors.yellow,
12    this.emptyColor = Colors.grey,
13    this.num = 5,
14    this.size = 25});
15}

2. 创建好画板准备开

1class _StarPainter extends CustomPainter {
2  Star star;
3  Paint _paint;
4  Paint _filePaint;
5  Path _path;
6  double _radius;
7
8  _StarPainter(this.star) {
9    _paint = Paint()
10      ..color = (star.emptyColor)
11      ..isAntiAlias = true;
12
13    _filePaint = Paint()
14      ..color = (star.fillColor);
15    _path = Path();
16    _radius = star.size / 2.0;
17
18  }
19  @override
20  void paint(Canvas canvas, Size size) {
21    //TODO 绘制
22  }
23
24  @override
25  bool shouldRepaint(CustomPainter oldDelegate) {
26    return true;
27  }
28}

如果说StarWidget是评分组件的基础,那么绘制的路径就是星星的灵魂

下面的nStarPath是绘制n角星路径的核心方法

1  Path nStarPath(int num, double R, double r, {dx = 0, dy = 0, rotate = 0}) {
2    _path.reset(); //重置路径
3    double perRad = 2 * pi / num; //每份的角度
4    double radA = perRad / 2 / 2 + rotate; //a角
5    double radB = 2 * pi / (num - 1) / 2 - radA / 2 + radA + rotate; //起始b角
6    _path.moveTo(cos(radA) * R + dx, -sin(radA) * R + dy); //移动到起点
7    for (int i = 0; i < num; i++) {
8      //循环生成点,路径连至
9      _path.lineTo(
10          cos(radA + perRad * i) * R + dx, -sin(radA + perRad * i) * R + dy);
11      _path.lineTo(
12          cos(radB + perRad * i) * r + dx, -sin(radB + perRad * i) * r + dy);
13    }
14    _path.close();
15    return _path;
16  }

在初始化的时候为路径赋值

1class _StarPainter extends CustomPainter {
2 ...
3  _StarPainter(this.star) {
4    ...
5    _path = Path();
6    _radius = star.size / 2.0;
7    nStarPath(star.num, _radius, _radius * star.fat);
8  }

绘制也非常简单,其中有个进度问题,可以先画背景的星星,

再画一个填充的星星,再用canvas.clipRect进行裁剪即可。

1  @override
2  void paint(Canvas canvas, Size size) {
3    canvas.translate(_radius, _radius);
4    canvas.drawPath(_path, _paint);
5    canvas.clipRect(Rect.fromLTRB(
6        -_radius-_radius, _radius * 2 * star.progress - _radius, _radius));
7    canvas.drawPath(_path, _filePaint);
8  }

4.自定义组件

将画板放入CustomPaint中即可

1 class StarWidget extends StatelessWidget {
2  final Star star;
3
4  StarWidget({this.star = const Star()});
5
6  @override
7  Widget build(BuildContext context) {
8    return Container(
9      width: star.size,
10      height: star.size,
11      child: CustomPaint(
12        painter: _StarPainter(star),
13      ),
14    );
15  }
16}

这就是星星组件的所有代码,也不超过百行。

三 、StarScore的实现

该组件的目的是显示评分,可以精确的显示百分进度

其实也没啥,仅是用几个StarWidget拼起来而已,代码也就下面一丢丢

1class StarScore extends StatelessWidget {
2  final Star star;
3  final double score;
4  final Widget tail;
5
6  StarScore({this.star = const Star()this.score, this.tail});
7
8  @override
9  Widget build(BuildContext context) {
10    var li = [];
11    int count = score.floor();
12    for (int i = 0; i < count; i++) {
13      li.add(StarWidget(star: star.copyWith(progress: 1.0)));
14    }
15    if (score - count > 0) {
16      li.add(StarWidget(star: star.copyWith(progress: score - count)));
17    }
18    return Wrap(
19      crossAxisAlignment: WrapCrossAlignment.center,
20      children: [
21        ...li,
22        SizedBox(
23          width: 10,
24        ),
25        if (tail!=null) tail
26      ],
27    );
28  }
29}

四 、CustomRating 的实现

由于点击需要自己响应进行状态改变,所以使用StatefulWidget  最核心的是GestureDetector进行触点的获取并计算出当前值。其中对.5进行处理,以及越界的处理。

1class CustomRating extends StatefulWidget {
2  final int max;
3  final Star star;
4  final double score;
5  final Function(double) onRating;
6
7  CustomRating(
8      {this.max = 5,
9      this.score = 0,
10      this.star = const Star(),
11      @required this.onRating})
12      : assert(score <= max);
13
14  @override
15  _CustomRatingState createState() => _CustomRatingState();
16}
17
18class _CustomRatingState extends State<CustomRating{
19  double _score;
20
21  @override
22  void initState() {
23    _score = widget.score;
24    super.initState();
25  }
26
27  @override
28  Widget build(BuildContext context) {
29    var li = [];
30
31    int count = _score.floor(); //满星
32    for (int i = 0; i < count; i++) {
33      li.add(StarWidget(star: widget.star.copyWith(progress: 1.0)));
34    }
35
36    if (_score != widget.max.toDouble())
37      li.add(StarWidget(
38          star: widget.star.copyWith(progress: _score - count))); //不满星
39
40    var empty = widget.max - count - 1// 空星
41    for (int i = 0; i < empty; i++) {
42      li.add(StarWidget(star: widget.star.copyWith(progress: 0)));
43    }
44    return GestureDetector(
45      onTapDown: (d) {
46        setState(() {
47          _score = d.localPosition.dx / widget.star.size;
48          if (_score - _score.floor() > 0.5) {
49            _score = _score.floor() + 1.0;
50          } else {
51            _score = _score.floor() + 0.5;
52          }
53
54          if (_score >= widget.max.toDouble()) {
55            _score = widget.max.toDouble();
56          }
57          widget.onRating(_score);
58        });
59      },
60      child: Wrap(
61        children: li,
62      ),
63    );
64  }
65}

五. 发布到pub

需要在 pubspec.yaml 进行一些配置

1name 是名称
2description 是描述 60 ~ 180 之间,太短或太长会扣分
3version 版本
4author 作者信息,会报warning 但我就想写
5homepage 主页

1---->[pubspec.yaml]----
2name: flutter_star
3description: You can create a star easily and decide how many angle or color of the star, even the fat and progress of the star.
4version0.1.2
5author: 张风捷特烈<1981462002@qq.com>
6homepage: https://juejin.im/user/149189281194766/collections

2.最好写个example

不然会扣分

它会在这里,给使用者看

3.发布到pub

1flutter packages pub publish --server=https://pub.dartlang.org

然后需要权限验证,记得 全部复制在浏览器打开,不用在控制台点链接 ,由于控制台的换行而导致url不全。

然后就看你的网给不给力了。 flutter_star 欢迎使用