Code前端首页关于Code前端联系我们

Flutter开发并编写了一个前沿的星级检查器

terry 2年前 (2023-09-23) 阅读数 61 #移动小程序

工作中的需求之一是显示游戏评分,但Flutter不提供现成的评分。您还可以在线找到有关自定义评级控制的相关文章。有点违背我自己的需求,所以这里再写一篇文章。国际惯例,上图

Flutter开发编写一个高大上的星级评分控件

要点

关键是要有三点才能达到好的效果

  1. 未选星显示
  2. 满星显示
  3. 未满星显示

星级评定原则是使用未选星星作为背景,顶层使用满星和未满星覆盖显示,达到百分比效果

  1. 使用堆叠布局来控制两种样式的重叠
  2. 使用ClipRect裁剪显示未满星效果

详细实现

首先我们实现静态评分效果。

我们可以预先设计别人使用时可以修改的内容,比如星星数量、星星间距、星星大小等。这里我把我先写的组件参数提取出来。大家可以根据自己的需要进行扩展

不多说了,这里是代码

class RatingBar extends StatefulWidget {
  final int count; 
  final double maxRating;
  final double value;
  final double size;
  final double padding;
  final String nomalImage;
  final String selectImage;
  final bool selectAble;
  final ValueChanged<String> onRatingUpdate;

  RatingBar({
    this.maxRating = 10.0,
    this.count = 5,
    this.value = 10.0,
    this.size = 20,
    this.nomalImage,
    this.selectImage,
    this.padding,
    this.selectAble = false,
    @required this.onRatingUpdate
  }) : assert(nomalImage != null),
        assert(selectImage != null);

  @override
  _RatingBarState createState() => _RatingBarState();
}复制代码

1.背景星星

我们只做横向效果,所以可以用Row来打造星星排列效果

List<Widget> buildNomalRow() {
    List<Widget> children = [];
    for(int i = 0; i < widget.count; i ++) {
      children.add(Image.asset(widget.nomalImage,height: widget.size,width: widget.size,));
      if(i < widget.count - 1) {
        children.add(SizedBox(width: widget.padding,));
      }
    }
    return children;
}复制代码

每个添加 SizedBox 作为星星之间的间隔

2。满星和未满星

我们根据分数和最大星星数计算出每个星星对应的分数比例,然后根据分数比例计算满星和未满星的数量之间的裁剪比例

int fullStars() {
    if(value != null) {
      return (value /(widget.maxRating/widget.count)).floor();
    }
    return 0;
}

double star() {
    if(value != null) {
        if(widget.count / fullStars() == widget.maxRating / value ) {
        return 0;
        }
        return (value % (widget.maxRating/widget.count))/(widget.maxRating/widget.count);
    }
    return 0;
}

List<Widget> buildRow() {
    int full = fullStars();
    List<Widget> children = [];
    for(int i = 0; i < full; i ++) {
      children.add(Image.asset(widget.selectImage,height: widget.size,width: widget.size,));
      if(i < widget.count - 1) {
        children.add(SizedBox(width: widget.padding,),);
      }
    }
    if(full < widget.count) {
      children.add(ClipRect(
        clipper: SMClipper(rating: star() * widget.size),
        child: Image.asset(widget.selectImage,height: widget.size,width: widget.size),
      ));
    }
    return children;
}复制代码

计算满星数量:
每颗星星对应的分数值=最高分/星星数量
当前评分/星星得分四舍五入即为当前满星数量
未满星作物比例:
当前评分系统除以星星分数就是去掉满星后的分数。然后把这个值除以星星分数,就是当前未满星星星的裁剪比例

Crop

因为我们只需要垂直裁剪,所以使用ClipRect就可以满足需求

class SMClipper extends CustomClipper<Rect>{
  final double rating;
  SMClipper({
    this.rating
  }): assert(rating != null);
  @override
  Rect getClip(Size size) {
    return Rect.fromLTRB(0.0, 0.0, rating , size.height);
  }

  @override
  bool shouldReclip(SMClipper oldClipper) {
    return rating != oldClipper.rating;
  }
}复制代码

至此,静态评价显示控制的内容已经写好了。看看效果

Flutter开发编写一个高大上的星级评分控件

动态

现在分数显示出来了,肯定需要一个能得分的人。我们需要在原有的基础上监听控件的触摸和点击事件,并计算出触摸结束的点。 ,获取当前点代表的评估值

监听运动

如果你正在监听手势动作,我们可以使用Listener。这里我们需要用到它的两个回调方法onPointerMove和onPointerDown(或者onPointerUp)。第一个用于跟踪滑动,第二个用于跟踪点击

Listener(
      child: buildRowRating(),
      onPointerDown: (PointerDownEvent event){
        double x = event.localPosition.dx;
        if (x < 0) x = 0;
        pointValue(x);
      },
      onPointerMove: (PointerMoveEvent event) {
        double x = event.localPosition.dx;
        if (x < 0) x = 0;
        pointValue(x);
      },
      onPointerUp: (_) {
      },
      behavior: HitTestBehavior.deferToChild,
    )复制代码

根据手势的 x 坐标计算当前手指位置代表的评估值,获得后开始重建。这里的问题是去掉星星之间的间隔以保证准确性

pointValue(double dx) {
    if(!widget.selectAble) {
      return;
    }
    if(dx >= widget.size * widget.count  + widget.padding * (widget.count - 1)) {
      value = widget.maxRating;
    }else {
      for(double i = 1; i < widget.count + 1;i ++) {
        if(dx > widget.size * i + widget.padding *(i -1) && dx < widget.size * i + widget.padding * i) {
          value = i * (widget.maxRating/widget.count);
          break;
        }else if(dx > widget.size * (i -1) + widget.padding*(i -1) && dx < widget.size * i+ widget.padding*i )  {
          value = (dx - widget.padding *(i -1))/(widget.size * widget.count ) *widget.maxRating;
          break;
        }
      }
    }
    setState(() {
      widget.onRatingUpdate(value.toStringAsFixed(1));
    });
  }复制代码

这样可以动态评分的空间就被填满了。使用方法见下文

RatingBar(
    value: 9,
    size: 30,
     padding: 5,
     nomalImage: 'img/star_nomal.png',
     selectImage: 'img/star.png',
     selectAble: true,
     onRatingUpdate: (value) {},
     maxRating: 10,
     count: 6,
     )复制代码

各参数说明

  • value:当前评级值
  • size:星星大小
  • padding:星星之间的间隙
  • nomalImage:空星星图像
  • selectAble :是否可以点击滑动编辑评分值
  • onRatingUpdate:点击滑动编辑回调评分值,参数为 String 评分值
  • maxRating:最大评分值
  • number:星星数

作者:sponmas
链接:https://juejin.im/post/5d6f9349f265da03f66de01b
来源:掘金
如需商业转载请联系作者授权。非商业转载请注明来源。

版权声明

本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。

发表评论:

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

热门