Loading

# Flutter星级评选,星级,星级排行

星级评选,星级,星级排行

整一个豆瓣的星级评选,效果图和原图如下

img

自己做的效果.png

img

豆瓣评分效果.png

一、开发步骤

首先,明确一下如何来设计这个东西。
评分系统,一看图上就有两种不同的星星,一个表层的带颜色的星星,一个底层的灰色星星。
底层的灰色星星,都是满星,而表层的带颜色的星星,有半颗的。
这时候我们就知道了,要做一个层叠在一起的两组星星,一组底层的灰色满的,一组表层的可能不是满的。

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

img

CustomClipper.png

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 子类中,我们有两个必须要实现的方法getClipshouldReclip
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;
  }

}

其他文章

作者:大眠兽
链接:https://www.jianshu.com/p/82c3871bb6f5
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

posted @ 2020-12-23 10:56  bopo  阅读(214)  评论(0编辑  收藏  举报