【 开源计划 - Flutter组件 】 星星也可以如此闪耀 flutter_star

3,436 阅读4分钟

pub地址 】 【github地址

dependencies:
  flutter_star: $lastVersion

一、描述

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

---->[StarScore 星星显示组件]----
[1] 比如显示4.2: 会有5颗星, 前四颗填满,后一刻填充20%
StarScore 为 Stateless组件,仅负责显示的需求

---->[CustomRating 星星评分组件]----
[2] 可指定最大值,也就是显示多少个星星
[3] 点击时会改变状态,进行评分,支持半星评分
[4] 支持评分回调

---->[StarWidget组件]----
[5]. 可定义星星的显示进度情况 0% ~ 100 % 无死角
[6]. 可定义星星的角数
[7]. 可定义星星的颜色、大小 

二 、StarScore

分数展示组件

名称类型功能备注默认
scoredouble分数-0
starStar第四点星星属性配置Star()
tailWidget尾部的组件-null
StarScore(
  score: 4.8,
  star: Star(
      fillColor: Colors.tealAccent,
      emptyColor: Colors.grey.withAlpha(88)),
  tail: Column(
    children: <Widget>[
      Text("综合评分"),
      Text("4.8"),
    ],
  ),
),


三 、CustomRating

评分组件

名称类型功能备注默认
maxint最大星星数-5
scoredouble分数-0
starStar第四点星星属性配置Star()
onRatingFluction(double)点击回调@requirednull
1.最简使用

CustomRating(onRating: (s) {
   print(s);
 }),

2.可高度定制

CustomRating(
     max: 6,
     score: 3.0,
     star: Star(
         num: 12,
         fillColor: Colors.orangeAccent,
         fat: 0.6,
         emptyColor: Colors.grey.withAlpha(88)),
    onRating: (s) {
       print(s);
     }),

四 、Star

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

名称类型功能备注默认
progressdouble填充的进度[0,1]0.0
numint星星的角数大于35
fatdouble星星的胖瘦(0,1]0.5
emptyColorColor星星的色-Colors.grey
fillColorColor星星的填充色-Colors.yellow
sizedouble星星的大小-20
1. 进度填充:progress


2. 星星的角数:num


3. 星星的胖瘦:fat


4. 星星的颜色:fillColor和emptyColor

展示结束,下面进入正文


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

1.分析组件的需求,抽离出需要配置的属性
class Star {
  final int num;
  final double progress;
  final Color emptyColor;
  final Color fillColor;
  final double size;
  final double fat;

  const Star({this.progress = 0,
    this.fat = 0.5,
      this.fillColor = Colors.yellow,
    this.emptyColor = Colors.grey,
    this.num = 5,
    this.size = 25});
}

2. 创建好画板准备开画
class _StarPainter extends CustomPainter {
  Star star;
  Paint _paint;
  Paint _filePaint;
  Path _path;
  double _radius;

  _StarPainter(this.star) {
    _paint = Paint()
      ..color = (star.emptyColor)
      ..isAntiAlias = true;

    _filePaint = Paint()
      ..color = (star.fillColor);
    _path = Path();
    _radius = star.size / 2.0;

  }
  @override
  void paint(Canvas canvas, Size size) {
    //TODO 绘制
  }

  @override
  bool shouldRepaint(CustomPainter oldDelegate) {
    return true;
  }
}

3.开画

如果说StarWidget是评分组件的基础,那么绘制的路径就是星星的灵魂
下面的nStarPath是绘制n角星路径的核心方法

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

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

class _StarPainter extends CustomPainter {
 ...
  _StarPainter(this.star) {
    ...
    _path = Path();
    _radius = star.size / 2.0;
    nStarPath(star.num, _radius, _radius * star.fat);
  }

绘制也非常简单,其中有个进度问题,可以先画背景的星星, 再画一个填充的星星,再用canvas.clipRect进行裁剪即可。

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

4.自定义组件

将画板放入CustomPaint中即可

 class StarWidget extends StatelessWidget {
  final Star star;

  StarWidget({this.star = const Star()});

  @override
  Widget build(BuildContext context) {
    return Container(
      width: star.size,
      height: star.size,
      child: CustomPaint(
        painter: _StarPainter(star),
      ),
    );
  }
}

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


二 、StarScore的实现

该组件的目的是显示评分,可以精确的显示百分进度
其实也没啥,仅是用几个StarWidget拼起来而已,代码也就下面一丢丢

class StarScore extends StatelessWidget {
  final Star star;
  final double score;
  final Widget tail;

  StarScore({this.star = const Star(), this.score, this.tail});

  @override
  Widget build(BuildContext context) {
    var li = <StarWidget>[];
    int count = score.floor();
    for (int i = 0; i < count; i++) {
      li.add(StarWidget(star: star.copyWith(progress: 1.0)));
    }
    if (score - count > 0) {
      li.add(StarWidget(star: star.copyWith(progress: score - count)));
    }
    return Wrap(
      crossAxisAlignment: WrapCrossAlignment.center,
      children: [
        ...li,
        SizedBox(
          width: 10,
        ),
        if (tail!=null) tail
      ],
    );
  }
}

三 、StarScore的实现

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

class CustomRating extends StatefulWidget {
  final int max;
  final Star star;
  final double score;
  final Function(double) onRating;

  CustomRating(
      {this.max = 5,
      this.score = 0,
      this.star = const Star(),
      @required this.onRating})
      : assert(score <= max);

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

class _CustomRatingState extends State<CustomRating> {
  double _score;

  @override
  void initState() {
    _score = widget.score;
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    var li = <StarWidget>[];

    int count = _score.floor(); //满星
    for (int i = 0; i < count; i++) {
      li.add(StarWidget(star: widget.star.copyWith(progress: 1.0)));
    }

    if (_score != widget.max.toDouble())
      li.add(StarWidget(
          star: widget.star.copyWith(progress: _score - count))); //不满星

    var empty = widget.max - count - 1; // 空星
    for (int i = 0; i < empty; i++) {
      li.add(StarWidget(star: widget.star.copyWith(progress: 0)));
    }
    return GestureDetector(
      onTapDown: (d) {
        setState(() {
          _score = d.localPosition.dx / widget.star.size;
          if (_score - _score.floor() > 0.5) {
            _score = _score.floor() + 1.0;
          } else {
            _score = _score.floor() + 0.5;
          }

          if (_score >= widget.max.toDouble()) {
            _score = widget.max.toDouble();
          }
          widget.onRating(_score);
        });
      },
      child: Wrap(
        children: li,
      ),
    );
  }
}

四. 发布到pub

1.配置

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

name 是名称
description 是描述 60 ~ 180 之间,太短或太长会扣分
version 版本
author 作者信息,会报warning 但我就想写
homepage 主页
---->[pubspec.yaml]----
name: flutter_star
description: You can create a star easily and decide how many angle or color of the star, even the fat and progress of the star.
version: 0.1.2
author: 张风捷特烈<1981462002@qq.com>
homepage: https://juejin.cn/user/149189281194766

2.最好写个example

不然会扣分

它会在这里,给使用者看


3.发布到pub
flutter packages pub publish --server=https://pub.dartlang.org

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

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


尾声

另外本人有一个Flutter微信交流群,欢迎小伙伴加入,共同探讨Flutter的问题,期待与你的交流与切磋。

@张风捷特烈 2019.02.23 未允禁转
我的公众号:编程之王
联系我--邮箱:1981462002@qq.com --微信:zdl1994328
~ END ~