【问题标题】:Why do I get that point in my Flutter AnimatedChecked? (CustomPaint)为什么我在 Flutter AnimatedChecked 中得到了这一点? (自定义油漆)
【发布时间】:2020-11-09 04:28:13
【问题描述】:

免责声明

  • Flutter 测试版 - 1.19
  • Chrome 上的 Flutter Web

向所有 Flutter 开发者致敬 :)

我得到了一个动画检查小部件的解决方案(最初来自:Raul Mateo),我已经改进了它的行为和外观。

但我不是CustomPaint专家,所以我不知道在下面的代码中,为什么我在选中的行的转弯处得到那个绿点:

错误

代码

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;
  }
}

【问题讨论】:

    标签: android ios flutter web dart


    【解决方案1】:

    这似乎与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);
    

    但首先您想将 _offsetupdate 从 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;
      }
    }
    
    

    【讨论】:

    • 非常感谢@MDemetrio :)!像魅力一样工作!你是怎么知道的?总是这些额外的网络平台^^
    • 我也不知道!哈哈,我刚刚在 ios 上运行了你的代码,发现没有画出重点。我们可能应该在 Flutter 的 github 上提交一个关于此的问题,在 android 上也没有绘制这一点。
    猜你喜欢
    • 2023-04-05
    • 1970-01-01
    • 2011-08-27
    • 1970-01-01
    • 2021-11-02
    • 1970-01-01
    • 1970-01-01
    • 2012-04-02
    • 1970-01-01
    相关资源
    最近更新 更多