android - 为什么我在 Flutter AnimatedChecked 中得到了这一点? (自定义油漆)

标签 android ios flutter web dart

免责声明 :

  • Flutter Beta - 1.19
  • Chrome 上的 Flutter Web

  • 向所有 Flutter 开发人员问好 :)
    我得到了一个动画检查小部件的解决方案(最初由: Raul Mateo ),我已经改进了它的行为和外观。
    但我不是 CustomPaint 专家,所以我不知道在下面的代码中,为什么我在选中线的转弯处得到那个绿点:
    漏洞
    enter image description here
    代码
    import 'package:flutter/material.dart';
    import 'package:angles/angles.dart';
    import 'dart:math';
    import 'dart:core';
    
    class AnimatedCheck extends StatefulWidget {
      final double size;
      final Color color;
      final double strokeWidth;
      final VoidCallback onComplete;
    
      const AnimatedCheck({
        this.size = 80.0,
        this.onComplete,
        this.strokeWidth = 2.5,
        this.color = Colors.green,
      });
    
      @override
      _AnimatedCheckState createState() => _AnimatedCheckState();
    }
    
    class _AnimatedCheckState extends State<AnimatedCheck> with SingleTickerProviderStateMixin {
      AnimationController _controller;
      Animation<double> curve;
      bool completed = false;
    
      @override
      void initState() {
        super.initState();
        _controller = AnimationController(duration: Duration(seconds: 2), vsync: this);
        curve = CurvedAnimation(parent: _controller, curve: Curves.decelerate);
    
        _controller.addListener(() {
          if (_controller.status == AnimationStatus.completed && widget.onComplete != null) {
            widget.onComplete();
          }
          if (_controller.status == AnimationStatus.completed) {
            completed = true;
          }
          setState(() {});
        });
        _controller.forward();
      }
    
      @override
      Widget build(BuildContext context) {
        return Stack(
          alignment: Alignment.center,
          fit: StackFit.passthrough,
          children: [
            AnimatedContainer(
              duration: const Duration(milliseconds: 250),
              height: widget.size,
              width: widget.size,
              decoration: BoxDecoration(
                shape: BoxShape.circle,
                color: Colors.transparent,
                border: Border.all(
                  width: widget.strokeWidth,
                  color: completed ? widget.color : widget.color.withOpacity(.05),
                ),
              ),
            ),
            Container(
              height: widget.size - widget.strokeWidth,
              width: widget.size - widget.strokeWidth,
              child: CustomPaint(
                painter: CheckPainter(
                  value: curve.value,
                  color: widget.color,
                  strokeWidth: widget.strokeWidth,
                ),
              ),
            ),
          ],
        );
      }
    
      @override
      void dispose() {
        _controller.dispose();
        super.dispose();
      }
    }
    
    class CheckPainter extends CustomPainter {
      Paint _paint;
      double value;
      Color color;
      double strokeWidth;
    
      double _length;
      double _offset;
      double _secondOffset;
      double _startingAngle;
    
      CheckPainter({
        this.value,
        this.color,
        this.strokeWidth,
      }) {
        _paint = Paint()
          ..color = color
          ..strokeWidth = strokeWidth
          ..strokeCap = StrokeCap.round
          ..style = PaintingStyle.stroke;
        assert(value != null);
    
        _length = 60;
        _offset = 0;
        _startingAngle = 205;
      }
    
      @override
      void paint(Canvas canvas, Size size) {
        // Background canvas
        var rect = Offset(0, 0) & size;
        _paint.color = color.withOpacity(0);
    
        double line1x1 = size.width / 2 + size.width * cos(Angle.fromDegrees(_startingAngle).radians) * .5;
        double line1y1 = size.height / 2 + size.height * sin(Angle.fromDegrees(_startingAngle).radians) * .5;
        double line1x2 = size.width * .45;
        double line1y2 = size.height * .65;
    
        double line2x1 = size.width / 2 + size.width * cos(Angle.fromDegrees(320).radians) * .35;
        double line2y1 = size.height / 2 + size.height * sin(Angle.fromDegrees(320).radians) * .35;
    
        canvas.drawArc(rect, Angle.fromDegrees(_startingAngle).radians, Angle.fromDegrees(360).radians, false, _paint);
        canvas.drawLine(Offset(line1x1, line1y1), Offset(line1x2, line1y2), _paint);
        canvas.drawLine(Offset(line2x1, line2y1), Offset(line1x2, line1y2), _paint);
    
        // animation painter
    
        double circleValue, checkValue;
        if (value < .5) {
          checkValue = 0;
          circleValue = value / .5;
        } else {
          checkValue = (value - .5) / .5;
          circleValue = 1;
        }
    
        _paint.color = color;
        double firstAngle = _startingAngle + 360 * circleValue;
        double line1Value = 0, line2Value = 0;
    
        canvas.drawArc(rect, Angle.fromDegrees(firstAngle).radians,
            Angle.fromDegrees(getSecondAngle(firstAngle, _length, _startingAngle + 360)).radians, false, _paint);
    
        if (circleValue >= 1) {
          if (checkValue < .5) {
            line2Value = 0;
            line1Value = checkValue / .5;
          } else {
            line2Value = (checkValue - .55) / .55;
            line1Value = .75;
          }
        }
    
        double auxLine1x1 = (line1x2 - line1x1) * getMin(line1Value, .8);
        double auxLine1y1 = (((auxLine1x1) - line1x1) / (line1x2 - line1x1)) * (line1y2 - line1y1) + line1y1;
    
        if (_offset < 60) {
          auxLine1x1 = line1x1;
          auxLine1y1 = line1y1;
        }
    
        double auxLine1x2 = auxLine1x1 + _offset / 2;
        double auxLine1y2 = (((auxLine1x1 + _offset / 2) - line1x1) / (line1x2 - line1x1)) * (line1y2 - line1y1) + line1y1;
    
        if (checkIfPointHasCrossedLine(
            Offset(line1x2, line1y2), Offset(line2x1, line2y1), Offset(auxLine1x2, auxLine1y2))) {
          auxLine1x2 = line1x2;
          auxLine1y2 = line1y2;
        }
    
        if (_offset > 0) {
          canvas.drawLine(Offset(auxLine1x1, auxLine1y1), Offset(auxLine1x2, auxLine1y2), _paint);
        }
    
        // SECOND LINE
    
        double auxLine2x1 = (line2x1 - line1x2) * line2Value;
        double auxLine2y1 =
            ((((line2x1 - line1x2) * line2Value) - line1x2) / (line2x1 - line1x2)) * (line2y1 - line1y2) + line1y2;
    
        if (checkIfPointHasCrossedLine(
            Offset(line1x1, line1y1), Offset(line1x2, line1y2), Offset(auxLine2x1, auxLine2y1))) {
          auxLine2x1 = line1x2;
          auxLine2y1 = line1y2;
        }
        if (line2Value > 0) {
          canvas.drawLine(
              Offset(auxLine2x1, auxLine2y1),
              Offset(
                  (line2x1 - line1x2) * line2Value + _offset * .75,
                  ((((line2x1 - line1x2) * line2Value + _offset * .75) - line1x2) / (line2x1 - line1x2)) *
                          (line2y1 - line1y2) +
                      line1y2),
              _paint);
        }
      }
    
      double getMax(double x, double y) {
        return (x > y) ? x : y;
      }
    
      double getMin(double x, double y) {
        return (x > y) ? y : x;
      }
    
      bool checkIfPointHasCrossedLine(Offset a, Offset b, Offset point) {
        return ((b.dx - a.dx) * (point.dy - a.dy) - (b.dy - a.dy) * (point.dx - a.dx)) > 0;
      }
    
      double getSecondAngle(double angle, double plus, double max) {
        if (angle + plus > max) {
          _offset = angle + plus - max;
          return max - angle;
        } else {
          _offset = 0;
          return plus;
        }
      }
    
      @override
      bool shouldRepaint(CheckPainter old) {
        return old.value != value;
      }
    }
    
    

    最佳答案

    好像这和drawArc的web实现有关,下面的代码在ios上什么也没画,而是在web上画了一个点:

     canvas.drawArc(rect, 0, 0, false, _paint);
    
    您可以尝试有条件地绘制圆弧
        if (circleValue < 1)
          canvas.drawArc(
              rect,
              Angle.fromDegrees(firstAngle).radians,
              Angle.fromDegrees(
                      getSecondAngle(firstAngle, _length, _startingAngle + 360))
                  .radians,
              false,
              _paint);
    
    但首先你想移动 _offset更新自 getSecondAngle(...)到它自己的功能,因为它是两个独立的关注点
      double getUpdatedOffset(double angle, double plus, double max) =>
          angle + plus > max ? angle + plus - max : 0;
    
    并在条件 drawArc 之后调用它
        _offset = getUpdatedOffset(firstAngle, _length, _startingAngle + 360);
    
    完整更新的代码:
    import 'package:flutter/material.dart';
    import 'package:angles/angles.dart';
    import 'dart:math';
    import 'dart:core';
    
    class AnimatedCheck extends StatefulWidget {
      final double size;
      final Color color;
      final double strokeWidth;
      final VoidCallback onComplete;
    
      const AnimatedCheck({
        this.size = 80.0,
        this.onComplete,
        this.strokeWidth = 2.5,
        this.color = Colors.green,
      });
    
      @override
      _AnimatedCheckState createState() => _AnimatedCheckState();
    }
    
    class _AnimatedCheckState extends State<AnimatedCheck>
        with SingleTickerProviderStateMixin {
      AnimationController _controller;
      Animation<double> curve;
      bool completed = false;
    
      @override
      void initState() {
        super.initState();
        _controller =
            AnimationController(duration: Duration(seconds: 2), vsync: this);
        curve = CurvedAnimation(parent: _controller, curve: Curves.decelerate);
    
        _controller.addListener(() {
          if (_controller.status == AnimationStatus.completed &&
              widget.onComplete != null) {
            widget.onComplete();
          }
          if (_controller.status == AnimationStatus.completed) {
            completed = true;
          }
          setState(() {});
        });
        _controller.forward();
      }
    
      @override
      Widget build(BuildContext context) {
        return Stack(
          alignment: Alignment.center,
          fit: StackFit.passthrough,
          children: [
            AnimatedContainer(
              duration: const Duration(milliseconds: 250),
              height: widget.size,
              width: widget.size,
              decoration: BoxDecoration(
                shape: BoxShape.circle,
                color: Colors.transparent,
                border: Border.all(
                  width: widget.strokeWidth,
                  color: completed ? widget.color : widget.color.withOpacity(.05),
                ),
              ),
            ),
            Container(
              height: widget.size - widget.strokeWidth,
              width: widget.size - widget.strokeWidth,
              child: CustomPaint(
                painter: CheckPainter(
                  value: curve.value,
                  color: widget.color,
                  strokeWidth: widget.strokeWidth,
                ),
              ),
            ),
          ],
        );
      }
    
      @override
      void dispose() {
        _controller.dispose();
        super.dispose();
      }
    }
    
    class CheckPainter extends CustomPainter {
      Paint _paint;
      double value;
      Color color;
      double strokeWidth;
    
      double _length;
      double _offset;
      double _secondOffset;
      double _startingAngle;
    
      CheckPainter({
        this.value,
        this.color,
        this.strokeWidth,
      }) {
        _paint = Paint()
          ..color = color
          ..strokeWidth = strokeWidth
          ..strokeCap = StrokeCap.round
          ..style = PaintingStyle.stroke;
        assert(value != null);
    
        _length = 60;
        _offset = 0;
        _startingAngle = 205;
      }
    
      @override
      void paint(Canvas canvas, Size size) {
        // Background canvas
        var rect = Offset(0, 0) & size;
        _paint.color = color.withOpacity(0);
    
        double line1x1 = size.width / 2 +
            size.width * cos(Angle.fromDegrees(_startingAngle).radians) * .5;
        double line1y1 = size.height / 2 +
            size.height * sin(Angle.fromDegrees(_startingAngle).radians) * .5;
        double line1x2 = size.width * .45;
        double line1y2 = size.height * .65;
    
        double line2x1 =
            size.width / 2 + size.width * cos(Angle.fromDegrees(320).radians) * .35;
        double line2y1 = size.height / 2 +
            size.height * sin(Angle.fromDegrees(320).radians) * .35;
    
        canvas.drawArc(rect, Angle.fromDegrees(_startingAngle).radians,
            Angle.fromDegrees(360).radians, false, _paint);
        canvas.drawLine(Offset(line1x1, line1y1), Offset(line1x2, line1y2), _paint);
        canvas.drawLine(Offset(line2x1, line2y1), Offset(line1x2, line1y2), _paint);
    
        // animation painter
    
        double circleValue, checkValue;
        if (value < .5) {
          checkValue = 0;
          circleValue = value / .5;
        } else {
          checkValue = (value - .5) / .5;
          circleValue = 1;
        }
    
        _paint.color = color;
        double firstAngle = _startingAngle + 360 * circleValue;
        double line1Value = 0, line2Value = 0;
    
        if (circleValue < 1)
          canvas.drawArc(
              rect,
              Angle.fromDegrees(firstAngle).radians,
              Angle.fromDegrees(
                      getSecondAngle(firstAngle, _length, _startingAngle + 360))
                  .radians,
              false,
              _paint);
    
        _offset = getUpdatedOffset(firstAngle, _length, _startingAngle + 360);
    
        if (circleValue >= 1) {
          if (checkValue < .5) {
            line2Value = 0;
            line1Value = checkValue / .5;
          } else {
            line2Value = (checkValue - .55) / .55;
            line1Value = .75;
          }
        }
    
        double auxLine1x1 = (line1x2 - line1x1) * getMin(line1Value, .8);
        double auxLine1y1 =
            (((auxLine1x1) - line1x1) / (line1x2 - line1x1)) * (line1y2 - line1y1) +
                line1y1;
    
        if (_offset < 60) {
          auxLine1x1 = line1x1;
          auxLine1y1 = line1y1;
        }
    
        double auxLine1x2 = auxLine1x1 + _offset / 2;
        double auxLine1y2 =
            (((auxLine1x1 + _offset / 2) - line1x1) / (line1x2 - line1x1)) *
                    (line1y2 - line1y1) +
                line1y1;
    
        if (checkIfPointHasCrossedLine(Offset(line1x2, line1y2),
            Offset(line2x1, line2y1), Offset(auxLine1x2, auxLine1y2))) {
          auxLine1x2 = line1x2;
          auxLine1y2 = line1y2;
        }
    
        if (_offset > 0) {
          canvas.drawLine(Offset(auxLine1x1, auxLine1y1),
              Offset(auxLine1x2, auxLine1y2), _paint);
        }
    
        // SECOND LINE
    
        double auxLine2x1 = (line2x1 - line1x2) * line2Value;
        double auxLine2y1 =
            ((((line2x1 - line1x2) * line2Value) - line1x2) / (line2x1 - line1x2)) *
                    (line2y1 - line1y2) +
                line1y2;
    
        if (checkIfPointHasCrossedLine(Offset(line1x1, line1y1),
            Offset(line1x2, line1y2), Offset(auxLine2x1, auxLine2y1))) {
          auxLine2x1 = line1x2;
          auxLine2y1 = line1y2;
        }
        if (line2Value > 0) {
          canvas.drawLine(
              Offset(auxLine2x1, auxLine2y1),
              Offset(
                  (line2x1 - line1x2) * line2Value + _offset * .75,
                  ((((line2x1 - line1x2) * line2Value + _offset * .75) - line1x2) /
                              (line2x1 - line1x2)) *
                          (line2y1 - line1y2) +
                      line1y2),
              _paint);
        }
      }
    
      double getMax(double x, double y) {
        return (x > y) ? x : y;
      }
    
      double getMin(double x, double y) {
        return (x > y) ? y : x;
      }
    
      bool checkIfPointHasCrossedLine(Offset a, Offset b, Offset point) {
        return ((b.dx - a.dx) * (point.dy - a.dy) -
                (b.dy - a.dy) * (point.dx - a.dx)) >
            0;
      }
    
      double getSecondAngle(double angle, double plus, double max) =>
          angle + plus > max ? max - angle : plus;
    
      double getUpdatedOffset(double angle, double plus, double max) =>
          angle + plus > max ? angle + plus - max : 0;
    
      @override
      bool shouldRepaint(CheckPainter old) {
        return old.value != value;
      }
    }
    
    

    关于android - 为什么我在 Flutter AnimatedChecked 中得到了这一点? (自定义油漆),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/62986605/

    相关文章:

    android - 如何绘制两个标记之间的路径?

    ios - 将多个原型(prototype)单元格加载到 UITableView

    ios - 更改UISearchBar中的CLEAR按钮的淡色颜色NOT CANCEL BUTTON

    flutter - Flutter:ShowDialog无法与ListTile的OnTap()方法一起使用

    Flutter Messaging App - 禁用图标按钮

    flutter - 如何在 Flutter 中的选项卡之间发送数据?

    java - 我的代码中线性布局的父级是什么?

    android - 在 Recycler View 中单击项目时获取 TextView 的值?

    android - 打开文件的正确 mimeType

    ios - 为什么在 block 外设置时 `scheduledTimer` 会正确触发,而不是在 block 内?