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

2019-09-0507:21:10APP与小程序开发Comments3,903 views字数 3857阅读模式

工作中有一个需求是展示游戏评分,而Flutter系统并没有提供现成的评分控件,网上也能找到相关的自定义评分控件的文章,总是有点不符合我自己的需求,所以就有接下来的这篇文章了。国际惯例,上图文章源自菜鸟学院-https://www.cainiaoxueyuan.com/xcx/16084.html

Flutter开发编写一个高大上的星级评分控件文章源自菜鸟学院-https://www.cainiaoxueyuan.com/xcx/16084.html

关键点

关键是有三个点的效果要想好文章源自菜鸟学院-https://www.cainiaoxueyuan.com/xcx/16084.html

  1. 未选中星展示
  2. 满星展示
  3. 未满星展示

星际评分原理就是使用未选中星星作为背景,上层使用满星和未满星来覆盖展示,达到百分比的效果文章源自菜鸟学院-https://www.cainiaoxueyuan.com/xcx/16084.html

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

具体实现

首先我们实现一个静态的评分效果。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/xcx/16084.html

我们可以提前设计一下提供给其他人使用时,可以自定义的内容,比如说星星个数、星星的间距、星星的大小等等,这里我把自己写的组件参数先提取了,你们可以根据自己的需要来扩展文章源自菜鸟学院-https://www.cainiaoxueyuan.com/xcx/16084.html

话不多说,上代码文章源自菜鸟学院-https://www.cainiaoxueyuan.com/xcx/16084.html

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来做出星星的排列效果文章源自菜鸟学院-https://www.cainiaoxueyuan.com/xcx/16084.html

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作为间隔文章源自菜鸟学院-https://www.cainiaoxueyuan.com/xcx/16084.html

2、满星与未满星

我们根据评分和星星最大数量来计算每颗星星所对应的分数比例,然后根据分数比例来计算满星数量与未满星的裁剪比例文章源自菜鸟学院-https://www.cainiaoxueyuan.com/xcx/16084.html

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;
}复制代码

满星个数计算:
每颗星星对应分数值 = 最大分值/星星数量
当前评分 / 星星分数值 取整就是当前满星的个数
未满星裁剪比例:
当前评分制除以星星分数值取余,就是取出满星后的分数值,这个值再除以 星星分数值就是当前未满星星的裁剪比例文章源自菜鸟学院-https://www.cainiaoxueyuan.com/xcx/16084.html

裁剪

因为我们只需要竖向裁剪,所以用ClipRect就能满足需求了文章源自菜鸟学院-https://www.cainiaoxueyuan.com/xcx/16084.html

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;
  }
}复制代码

至此,一个静态的评分展示控件就已经写完了。看下效果文章源自菜鸟学院-https://www.cainiaoxueyuan.com/xcx/16084.html

Flutter开发编写一个高大上的星级评分控件文章源自菜鸟学院-https://www.cainiaoxueyuan.com/xcx/16084.html

动态

既然后展示评分,那肯定就需要一个能评分的了,我们就需要在原有的基础上对控件做一个 触摸和点击事件监听,对结束触摸的点进行计算,来得到当前点代表的评分值

监听移动

监听手势动作的话,我们就可以用到Listener了,这里需要用到它的两个回调方法onPointerMove和onPointerDown(或者onPointerUp),前者用来监听滑动,后者用来监听点击文章源自菜鸟学院-https://www.cainiaoxueyuan.com/xcx/16084.html

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坐标来计算当前手指位置代表的评分值,得到后触发重新构建,这里有个问题就是要去掉星星之间的间隔,保证精确文章源自菜鸟学院-https://www.cainiaoxueyuan.com/xcx/16084.html

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));
    });
  }复制代码

这样,一个可以动态评分的空间就完成了,怎么用呢,看下面文章源自菜鸟学院-https://www.cainiaoxueyuan.com/xcx/16084.html

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

各参数说明文章源自菜鸟学院-https://www.cainiaoxueyuan.com/xcx/16084.html

  • value:当前评分值
  • size:星星大小
  • padding:星星间距
  • nomalImage:空星图片
  • selectImage:满星图片
  • selectAble:是否可以点击滑动修改评分值
  • onRatingUpdate:点击滑动修改评分值回调,参数是String类型的评分值
  • maxRating:最大评分值
  • count:星星个数

作者:sponmas
链接:https://juejin.im/post/5d6f9349f265da03f66de01b
来源:掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/xcx/16084.html

文章源自菜鸟学院-https://www.cainiaoxueyuan.com/xcx/16084.html
  • 本站内容整理自互联网,仅提供信息存储空间服务,以方便学习之用。如对文章、图片、字体等版权有疑问,请在下方留言,管理员看到后,将第一时间进行处理。
  • 转载请务必保留本文链接:https://www.cainiaoxueyuan.com/xcx/16084.html

Comment

匿名网友 填写信息

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定