星星评价组件源码

library ff_stars;

import 'dart:math';
import 'package:fis_ui/define.dart';
import 'package:flutter/material.dart';

typedef FFStarsChanged = void Function(double realStars, double selectedStars);

///星号评分
class FFStars extends StatefulWidget implements FWidget {
  FFStars({
    required this.normalStar,
    required this.selectedStar,
    this.starCount = 5,
    this.defaultStars = 0.0,
    this.step = 0.01,
    this.rounded = false,
    this.starWidth = 30.0,
    this.starHeight = 30.0,
    this.starMargin = 10.0,
    this.miniStars = 0.0,
    this.justShow = false,
    this.starsChanged,
    this.followChange = false,
  });

  /// 未选中(默认)的星星.
  final Widget normalStar;

  /// 选中(高亮)的星星.
  final Widget selectedStar;

  /// 星星数量.
  final int starCount;

  /// 默认需要显示的星星数量(支持小数).
  final double defaultStars;

  /// 分阶, 范围0.01-1.0, 0.01表示任意星, 1.0表示整星星, 0.5表示半星, 范围内自定义.
  final double step;

  /// 星星的宽度.
  final double starWidth;

  /// 星星的高度.
  final double starHeight;

  /// 两个星星中间的间距.
  final double starMargin;

  /// 最低分, 字面意思.
  final double miniStars;

  /// 仅做展示, 如果是true则用户不可修改星星内容.
  final bool justShow;

  /// 数值发生变化的回调.
  final FFStarsChanged? starsChanged;

  /// 是否需要实时回调, 若开启, 拖动时会回调多次, 否则仅在用户操作结束后回调.
  final bool followChange;

  /// 四舍五入
  /// 默认false: 举例: step=1, 实际选择2.4则结果为: 3. step=0.5, 实际选择2.2则结果为2.5.("进一法")
  /// 为true时:  举例: step=1, 实际选择2.4则结果为: 2. step=0.5, 实际选择2.2则结果为2.0.("四舍五入-取最近值")
  final bool rounded;

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

class _FFStarsState extends State<FFStars> {
  late double _miniStars;

  late double _currentStars;

  late double _step;

  @override
  void initState() {
    super.initState();

    /// 限制0.01 <= step <= 1.0
    _step = min(1.0, widget.step);
    _step = max(0.01, widget.step);

    /// 限制最低星不高于最高星
    _miniStars = min(widget.miniStars, widget.starCount * 1.0);

    /// 限制当前星不高于最高星
    _currentStars = min(widget.defaultStars, widget.starCount * 1.0);

    /// 限制当前星不低于最低星
    _currentStars = max(widget.defaultStars, widget.miniStars);

    this.setupRealStars(_currentStars, false, false, false);
  }

  @override
  void didUpdateWidget(FFStars oldWidget) {
    setupRealStars(widget.defaultStars, false, false, false);
    super.didUpdateWidget(oldWidget);
  }

  @override
  Widget build(BuildContext context) {
    return Stack(
      children: [
        Listener(
          onPointerDown: (event) {
            if (widget.justShow) {
              return;
            }
            this.calculateSelectedStars(event.localPosition.dx, false);
          },
          onPointerMove: (event) {
            if (widget.justShow) {
              return;
            }
            this.calculateSelectedStars(event.localPosition.dx, false);
          },
          onPointerUp: (event) {
            if (widget.justShow) {
              return;
            }
            this.calculateSelectedStars(event.localPosition.dx, true);
          },
          child: Row(
            mainAxisSize: MainAxisSize.min,
            children: getStars(widget.starCount, false),
          ),
        ),
        IgnorePointer(
          child: ClipRect(
            clipper: FFStarsClipper(this.getRealWidth()),
            child: Row(
              mainAxisSize: MainAxisSize.min,
              children: getStars(widget.starCount, true),
            ),
          ),
        ),
      ],
    );
  }

  /// 计算实际选择了多少分
  void calculateSelectedStars(double width, bool needCallback) {
    /// 1.找到属于哪一颗星星
    var starIndex = (width / (widget.starWidth + widget.starMargin)).floor();

    /// 2.获取点击位置在星星的具体x坐标
    var locationX = min(
        width - starIndex * (widget.starWidth + widget.starMargin),
        widget.starWidth);

    /// 3.计算具体选中的分值.
    var selectedStars = starIndex + locationX / widget.starWidth;

    /// print("实际选择的分值为: " + selectedStars.toString());

    /// 4.计算实际得分
    this.setupRealStars(selectedStars, true, true, needCallback);
  }

  /// 设置最终的实际得分
  void setupRealStars(
      double selectedStars, bool useStep, bool reload, bool needCallback) {
    /// 1.最高分为星星个数, 最低分为自定义最低分(默认0);
    var realStars = min(widget.starCount, selectedStars);
    realStars = max(_miniStars, realStars);
    var i = realStars.floor();

    /// 2.根据分阶获取实际应该显示的分值, 用户选择的时候需要根据分阶, 开发者可以任意设置
    if (useStep == true) {
      var decimalNumber = (realStars - i);
      int floor = (decimalNumber / _step).floor();
      double remainder = decimalNumber % _step;

      if (widget.rounded == true) {
        /// 取最近值
        realStars =
            i + floor * _step + ((remainder >= _step * 0.5) ? _step : 0);
      } else {
        /// 进一法
        realStars = i + floor * _step + ((remainder > 0.0) ? _step : 0);
      }
    }
    _currentStars = (realStars * 100).floor() / 100;

    /// 3.更新星星展示效果并回调
    if (reload == true) {
      setState(() {});
      if (needCallback == false && widget.followChange == false) {
        return;
      }
      if (widget.starsChanged == null) {
        return;
      }
      widget.starsChanged!(_currentStars, selectedStars);
    }
  }

  /// 获取实际宽度
  double getRealWidth() {
    var i = _currentStars.floor();
    var width = (widget.starWidth + widget.starMargin) * i +
        (_currentStars - i) * widget.starWidth;
    return width;
  }

  /// 获取要显示的所有星星
  List<Widget> getStars(int count, bool selected) {
    return List.generate(max(count * 2 - 1, 0), (index) {
      if (index % 2 == 0) {
        return Container(
          width: widget.starWidth,
          height: widget.starHeight,
          child: selected ? widget.selectedStar : widget.normalStar,
        );
      }

      return Container(
        width: widget.starMargin,
        height: widget.starHeight,
        color: Colors.transparent,
      );
    });
  }
}

/// 剪切部分
class FFStarsClipper extends CustomClipper<Rect> {
  FFStarsClipper(this.width);

  double width;

  @override
  Rect getClip(Size size) {
    return Rect.fromLTRB(0, 0, width, size.height);
  }

  @override
  bool shouldReclip(covariant CustomClipper<Rect> oldClipper) {
    if (oldClipper is FFStarsClipper) {
      return oldClipper.width != this.width;
    }
    return false;
  }
}

// 引入import 'package:ff_stars/ff_stars.dart';

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值