简体   繁体   English

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

[英]Why do I get that point in my Flutter AnimatedChecked? (CustomPaint)

Disclaimer :免责声明

  • Flutter Beta - 1.19 Flutter 测试版 - 1.19
  • Flutter Web on Chrome Flutter Web on Chrome

Greetings to all Flutter devs:)问候所有 Flutter 开发人员:)

I got a solution for an Animated Checked Widgets (originally by: Raul Mateo ), which I have improved to behave and look better.我得到了一个 Animated Checked Widgets 的解决方案(最初由: Raul Mateo提供),我已经改进了它的行为和外观。

But I am not a CustomPaint expert, so I don't know in the following code, why I am getting that green point in the turn of the checked line:但我不是 CustomPaint 专家,所以我不知道在下面的代码中,为什么我在检查线的转弯处得到那个绿点:

Bug漏洞

在此处输入图像描述

Code代码

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

It seems like this has to do with the web implementation of drawArc, the following code draws nothing on ios but draws a point on web:似乎这与 drawArc 的 web 实现有关,以下代码在 ios 上没有绘制任何内容,但在 web 上绘制了一个点:

 canvas.drawArc(rect, 0, 0, false, _paint);

You can try to conditionally draw the arc您可以尝试有条件地绘制弧线

    if (circleValue < 1)
      canvas.drawArc(
          rect,
          Angle.fromDegrees(firstAngle).radians,
          Angle.fromDegrees(
                  getSecondAngle(firstAngle, _length, _startingAngle + 360))
              .radians,
          false,
          _paint);

but first you would like to to move the _offset update from getSecondAngle(...) to its own function, since it's two separate concerns但首先你想将_offset更新从getSecondAngle(...)移动到它自己的 function,因为这是两个独立的问题

  double getUpdatedOffset(double angle, double plus, double max) =>
      angle + plus > max ? angle + plus - max : 0;

and call it after the conditional drawArc并在条件 drawArc 之后调用它

    _offset = getUpdatedOffset(firstAngle, _length, _startingAngle + 360);

full updated code:完整更新的代码:

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM