flutter自定义画笔之贝塞尔圆形控件

166 阅读2分钟

flutter自定义画笔之贝塞尔圆形控件

「时光不负,创作不停,本文正在参加2022年中总结征文大赛

需求:根据外部传入的0-100展示不同的进度高度

贝塞尔线条模拟网站:aaaaaaaty.github.io/bezierMaker…

流程:

  • 先画一个圆
  • 利用cubicTo方法绘制三阶贝塞尔线条
  • 贝塞尔点的取点因为要根据外部传入的百分比绘制区域高度,所以根据三角函数计算出计算出x,y的取点进行弧度绘制

根据三角函数计算,至于角度a是多少可以根据,外部的百分比,计算他在整个圆弧上的所占比例得出sinA,和cosA的值 1656745231936.png

一、效果图:

image.png

二、代码


class LoadingCircleChangeWidget extends StatefulWidget {
  double waveRatio;
  int index = 0;

  LoadingCircleChangeWidget({required this.waveRatio, required this.index});

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

class _LoadingCircleChangeWidgetState extends State<LoadingCircleChangeWidget>
    with SingleTickerProviderStateMixin {
  Color waveColor = Color(0xFFF2F2F2);

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

  }

  @override
  Widget build(BuildContext context) {
    if (widget.waveRatio > 1) {
      widget.waveRatio = 1.0;
    }
    if (widget.waveRatio < 0) {
      widget.waveRatio = 0.0;
    }
    var viewText = widget.waveRatio > 0 ? "${widget.waveRatio * 100}%" : "未批";

    ///建立比例颜色
    _buildWaveColor();
    return Material(child: Container(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          Stack(
            alignment: AlignmentDirectional.center,
            children: [
              RepaintBoundary(
                //隔离重绘控件,singlescrollview 滑动发生重绘,导致画笔paint响应
                child: Container(
                    width: 200,
                    height: 200,
                    child: CustomPaint(
                        painter: _CircleWavePainter(
                            waveColor: waveColor,
                            waveRatio: widget.waveRatio))),
              ),
              Text("$viewText",
                  style: TextStyle(fontSize: 14, color: Color(0xFF262626))),
            ],
          ),
          Container(
            margin: EdgeInsets.only(top: 8),
            child: Text(
              "${widget.index + 1}",
              style: TextStyle(fontSize: 14, color: Color(0xFF595959)),
            ),
          ),
        ],
      ),
    ),);
  }

  ///建立比例颜色
  void _buildWaveColor() {
    var ratio = widget.waveRatio;
    if (ratio >= 0.85) {
      waveColor = Color(0xFF3B83FC);
    } else if (ratio >= 0.70 && ratio < 0.85) {
      waveColor = Color(0xFF32C8A9);
    } else if (ratio >= 0.60 && ratio < 0.70) {
      waveColor = Color(0xFFFFCB6B);
    } else if (ratio < 0.6) {
      waveColor = Color(0xFFFF6666);
    }
  }

  @override
  void dispose() {
    super.dispose();
  }
}

class _CircleWavePainter extends CustomPainter {
  ///波浪百分比
  double waveRatio = 0.0;
  Color waveColor = Colors.lightBlue;

  _CircleWavePainter({required this.waveRatio, required this.waveColor}):assert(waveRatio>=0&&waveRatio<=1.0);

  @override
  void paint(Canvas canvas, Size size) {
    final side = min(size.width, size.height);
    Paint _painter = Paint()
      ..color = Color(0xFFF2F2F2)
      ..style = PaintingStyle.fill
      ..isAntiAlias = true;

    //绘制背景圆圈
    final circlePath = Path();
    // 从哪开始 1从下开始, 2 从上开始 3 从左开始 4 从右开始  默认从下开始,
    // double start = pi / 2;
    // if (startType == 2) {
    //   start = -pi / 2;
    // } else if (startType == 3) {
    //   start = pi;
    // } else if (startType == 4) {
    //   start = pi * 2;
    // }
    circlePath.addArc(Rect.fromLTWH(0, 0, side, side), pi / 2, 2 * pi); //这里从下开始
    canvas.drawPath(circlePath, _painter);

    //绘制贝塞尔波浪
    var paint = Paint();
    paint.color = waveColor;
    paint.style = PaintingStyle.fill;
    paint.strokeWidth = 1;
    paint.isAntiAlias = true;
    waveRatio=0.6;
    //一半高度,可以看作半径
    double middleSide = side / 2;
    
    //一个完整的圆的弧度是2π。 360°=2π,所以1°=π/180
    if (waveRatio > 0.5) {

    //将90°的弧度(pi / 2)这里对应的是 50%~100%的高度区域
    //当前为60%-50%=10%,所以乘以2,弧度占比才对   
      var du = pi / 2 * (waveRatio - 0.5).abs() * 2; //在90°中弧度占了多少份
      print("du:$du");
      print("sinY:${sin(du)}=---cosx:${cos(du)}");
      //三角函数 计算出圆上角度点
      var duY = sin(du) * middleSide;
      var duX = cos(du) * middleSide;
      //计算出圆上角度点
      var startX = middleSide - duX;
      var startY = middleSide - duY;
      print("duX:$duX=---duY:$duY");
      print("startX:$startX=---starty:$startY");

      var endX = side - startX;
      var endY = startY;
      //贝塞尔点震荡弧度
      var controlPoint1X = startX + (middleSide - startX) *  (1 - waveRatio);
      var controlPoint1Y = startY + (1 - waveRatio) * (side) * (waveRatio);

      var controlPoint2X = middleSide + (middleSide - startX)  * (1 - waveRatio);
      var controlPoint2Y = startY - (1 - waveRatio) * (side) * (1 - waveRatio);
      print(
          "startX:$startX=---starty:$startY---controlPoint1X:$controlPoint1X--controlPoint1Y:$controlPoint1Y");
      print(
          "controlPoint2X:$controlPoint2X=---controlPoint2Y:$controlPoint2Y---endX:$endX--endY:$endY");
      var path = Path();
      path.moveTo(startX, startY);

      path.cubicTo(controlPoint1X, controlPoint1Y, controlPoint2X,
          controlPoint2Y, endX, endY);

      path.lineTo(size.width, endY); //绝对坐标位置
      path.lineTo(size.width, size.height); //绝对坐标位置
      path.lineTo(0, size.height); //绝对坐标位置
      path.lineTo(0, 0); //绝对坐标位置
      path.close();
      final combinePath =
          Path.combine(PathOperation.intersect, circlePath, path);
      canvas.drawPath(combinePath, paint);
    } else {
      var du = pi / 2 * (waveRatio - 0.5).abs() * 2; //在90°中弧度占了多少份

      print("du:$du");
      print("sinY:${sin(du)}=---cosx:${cos(du)}");
      var duY = sin(du) * middleSide;
      var duX = cos(du) * middleSide;
      print("duX:$duX=---duY:$duY");
      //计算出圆上角度点
      var startX = middleSide - duX;
      var startY = middleSide + duY;

      var endX = side - startX;
      var endY = startY;
      //
      var controlPoint1X = startX + (middleSide - startX) / 2;
      var controlPoint1Y = startY + (waveRatio) * side * (waveRatio);

      var controlPoint2X = middleSide + (middleSide - startX) / 6;
      var controlPoint2Y = startY - (waveRatio) * side * (waveRatio);
      print(
          "startX:$startX=---starty:$startY---controlPoint1X:$controlPoint1X--controlPoint1Y:$controlPoint1Y");
      print(
          "controlPoint2X:$controlPoint2X=---controlPoint2Y:$controlPoint2Y---endX:$endX--endY:$endY");
      var path = Path();
      path.moveTo(startX, startY);

      path.cubicTo(controlPoint1X, controlPoint1Y, controlPoint2X,
          controlPoint2Y, endX, endY);

      path.lineTo(size.width, size.height); //绝对坐标位置
      path.lineTo(0, size.height); //绝对坐标位置
      path.close();
      final combinePath =
          Path.combine(PathOperation.intersect, circlePath, path);
      canvas.drawPath(combinePath, paint);
    }
  }

  @override
  bool shouldRepaint(covariant _CircleWavePainter oldDelegate) {
    return oldDelegate.waveRatio != waveRatio || oldDelegate.waveColor != waveColor;
  }
}

参考: juejin.cn/post/684490… juejin.cn/post/708718…