Flutter动画—雷达扫描效果

前言

我们现在要用Flutter做一个雷达扫描的动画,如下图所示

需求分析

  • 需要在画布上画出三个同心圆和一个十字
  • 创建一个固定角度的圆弧
  • 圆弧做渐变色
  • 让圆弧动起来
  • 封装组件,将圆弧角度、圆弧颜色、几个同心圆与十字颜色

实现步骤

1.创建一3个同心圆与十字

复制代码
class RingPainter extends CustomPainter {
  RingPainter();
  final double angle = 0;
  final Color radarViewColor = Colors.pink; //雷达扫描指针的颜色
  final int circleCount = 3; //雷达扫描添加瞄准的环数,暂时注销了
 
  final Paint _paint = Paint()..style = PaintingStyle.fill;
 
  @override
  void paint(Canvas canvas, Size size) {
    var radius = min(size.width / 2, size.height / 2);
    Paint _bgPaint = Paint()
      ..color = radarViewColor
      ..strokeWidth = 1
      ..style = PaintingStyle.stroke;
    //十字架部分
    canvas.drawLine(
      Offset(size.width / 2, size.height / 2 - radius),
      Offset(size.width / 2, size.height / 2 + radius),
      _bgPaint,
    );
    canvas.drawLine(
      Offset(size.width / 2 - radius, size.height / 2),
      Offset(size.width / 2 + radius, size.height / 2),
      _bgPaint,
    );
    //同心圆部分
    for (var i = 1; i <= circleCount; ++i) {
      canvas.drawCircle(Offset(size.width / 2, size.height / 2),
          radius * i / circleCount, _bgPaint);
    }
    canvas.save();
    canvas.restore();
  }
 
  @override
  bool shouldRepaint(CustomPainter oldDelegate) {
    return true;
  }
}
 
 
class TextLiany extends StatefulWidget {
  const TextLiany({super.key});
 
  @override
  State<TextLiany> createState() => _TextLianyState();
}
 
class _TextLianyState extends State<TextLiany> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Container(
            width: 300,
            height: 300,
            // child: RadarView(),
            child: CustomPaint(
              painter: RingPainter(),
            )),
      ),
    );
  }
}

2.修改RingPainter类,画出一个90°圆弧,并且旋转30°

复制代码
class RingPainter extends CustomPainter {
  RingPainter();
  final double angle = pi / 180 * 10;
  final Color radarViewColor = Colors.pink; //雷达扫描指针的颜色
  final int circleCount = 3; //雷达扫描添加瞄准的环数,暂时注销了
 
  final Paint _paint = Paint()..style = PaintingStyle.fill;
 
  @override
  void paint(Canvas canvas, Size size) {
    var radius = min(size.width / 2, size.height / 2);
    Paint _bgPaint = Paint()
      ..color = radarViewColor
      ..strokeWidth = 1
      ..style = PaintingStyle.stroke;
    //十字架部分
    canvas.drawLine(
      Offset(size.width / 2, size.height / 2 - radius),
      Offset(size.width / 2, size.height / 2 + radius),
      _bgPaint,
    );
    canvas.drawLine(
      Offset(size.width / 2 - radius, size.height / 2),
      Offset(size.width / 2 + radius, size.height / 2),
      _bgPaint,
    );
    //同心圆部分
    for (var i = 1; i <= circleCount; ++i) {
      canvas.drawCircle(Offset(size.width / 2, size.height / 2),
          radius * i / circleCount, _bgPaint);
    }
    canvas.save();
    double r = sqrt(pow(size.width, 2) + pow(size.height, 2));
    double startAngle = atan(size.height / size.width);
    Point p0 = Point(r * cos(startAngle), r * sin(startAngle));
    Point px = Point(r * cos(angle + startAngle), r * sin(angle + startAngle));
    canvas.translate((p0.x - px.x) / 2, (p0.y - px.y) / 2);
    canvas.rotate(angle);
    canvas.drawArc(
        Rect.fromCircle(
            center: Offset(size.width / 2, size.height / 2), radius: radius),
        0,
        pi / 180 * 90,
        true,
        _paint);
    canvas.restore();
  }
 
  @override
  bool shouldRepaint(CustomPainter oldDelegate) {
    return true;
  }
}

3.给圆弧设置渐变色,只需要给_paint添加渐变色即可

复制代码
_paint.shader = ui.Gradient.sweep(
       Offset(size.width / 2, size.height / 2),
       [radarViewColor.withOpacity(.01), radarViewColor.withOpacity(.5)],
       [.0, 1.0],
       TileMode.clamp,
       .0,
       pi / 180 * 30);

class RingPainter extends CustomPainter {
  RingPainter();
  final double angle = pi / 180 * 10;
  final Color radarViewColor = Colors.pink; //雷达扫描指针的颜色
  final int circleCount = 3; //雷达扫描添加瞄准的环数,暂时注销了
 
  final Paint _paint = Paint()..style = PaintingStyle.fill;
 
  @override
  void paint(Canvas canvas, Size size) {
    var radius = min(size.width / 2, size.height / 2);
    Paint _bgPaint = Paint()
      ..color = radarViewColor
      ..strokeWidth = 1
      ..style = PaintingStyle.stroke;
    //十字架部分
    canvas.drawLine(
      Offset(size.width / 2, size.height / 2 - radius),
      Offset(size.width / 2, size.height / 2 + radius),
      _bgPaint,
    );
    canvas.drawLine(
      Offset(size.width / 2 - radius, size.height / 2),
      Offset(size.width / 2 + radius, size.height / 2),
      _bgPaint,
    );
    //同心圆部分
    for (var i = 1; i <= circleCount; ++i) {
      canvas.drawCircle(Offset(size.width / 2, size.height / 2),
          radius * i / circleCount, _bgPaint);
    }
    _paint.shader = ui.Gradient.sweep(
        Offset(size.width / 2, size.height / 2),
        [radarViewColor.withOpacity(.01), radarViewColor.withOpacity(.5)],
        [.0, 1.0],
        TileMode.clamp,
        .0,
        pi / 180 * 30);
    canvas.save();
    double r = sqrt(pow(size.width, 2) + pow(size.height, 2));
    double startAngle = atan(size.height / size.width);
    Point p0 = Point(r * cos(startAngle), r * sin(startAngle));
    Point px = Point(r * cos(angle + startAngle), r * sin(angle + startAngle));
    canvas.translate((p0.x - px.x) / 2, (p0.y - px.y) / 2);
    canvas.rotate(angle);
    canvas.drawArc(
        Rect.fromCircle(
            center: Offset(size.width / 2, size.height / 2), radius: radius),
        0,
        pi / 180 * 90,
        true,
        _paint);
    canvas.restore();
  }
 
  @override
  bool shouldRepaint(CustomPainter oldDelegate) {
    return true;
  }
}

4.创建动画,然后将旋转角度的angle从固定值转换成动画数值即可

复制代码
class RingPainter extends CustomPainter {
  RingPainter({this.angle = pi / 180 * 10});
  final double angle;
  final Color radarViewColor = Colors.pink; //雷达扫描指针的颜色
  final int circleCount = 3; //雷达扫描添加瞄准的环数,暂时注销了
 
  final Paint _paint = Paint()..style = PaintingStyle.fill;
 
  @override
  void paint(Canvas canvas, Size size) {
    var radius = min(size.width / 2, size.height / 2);
    Paint _bgPaint = Paint()
      ..color = radarViewColor
      ..strokeWidth = 1
      ..style = PaintingStyle.stroke;
    //十字架部分
    canvas.drawLine(
      Offset(size.width / 2, size.height / 2 - radius),
      Offset(size.width / 2, size.height / 2 + radius),
      _bgPaint,
    );
    canvas.drawLine(
      Offset(size.width / 2 - radius, size.height / 2),
      Offset(size.width / 2 + radius, size.height / 2),
      _bgPaint,
    );
    //同心圆部分
    for (var i = 1; i <= circleCount; ++i) {
      canvas.drawCircle(Offset(size.width / 2, size.height / 2),
          radius * i / circleCount, _bgPaint);
    }
    _paint.shader = ui.Gradient.sweep(
        Offset(size.width / 2, size.height / 2),
        [radarViewColor.withOpacity(.01), radarViewColor.withOpacity(.5)],
        [.0, 1.0],
        TileMode.clamp,
        .0,
        pi / 180 * 30);
    canvas.save();
    double r = sqrt(pow(size.width, 2) + pow(size.height, 2));
    double startAngle = atan(size.height / size.width);
    Point p0 = Point(r * cos(startAngle), r * sin(startAngle));
    Point px = Point(r * cos(angle + startAngle), r * sin(angle + startAngle));
    canvas.translate((p0.x - px.x) / 2, (p0.y - px.y) / 2);
    canvas.rotate(angle);
    canvas.drawArc(
        Rect.fromCircle(
            center: Offset(size.width / 2, size.height / 2), radius: radius),
        0,
        pi / 180 * 90,
        true,
        _paint);
    canvas.restore();
  }
 
  @override
  bool shouldRepaint(CustomPainter oldDelegate) {
    return true;
  }
}
 
 
class _TextLianyState extends State<TextLiany>
    with SingleTickerProviderStateMixin {
  late AnimationController _controller;
  late Animation<double> _animation;
  @override
  void initState() {
    _controller =
        AnimationController(vsync: this, duration: const Duration(seconds: 3));
    _animation = Tween(begin: .0, end: pi * 2).animate(_controller);
    _controller.repeat();
    super.initState();
  }
 
  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }
 
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Container(
            width: 300,
            height: 300,
            // child: RadarView(),
            child: AnimatedBuilder(
              animation: _animation,
              builder: (context, child) {
                return CustomPaint(
                  painter: RingPainter(angle: _animation.value),
                );
              },
            )),
      ),
    );
  }
}
相关推荐
孤鸿玉5 小时前
Fluter InteractiveViewer 与ScrollView滑动冲突问题解决
flutter
叽哥11 小时前
Flutter Riverpod上手指南
android·flutter·ios
BG1 天前
Flutter 简仿Excel表格组件介绍
flutter
zhangmeng1 天前
FlutterBoost在iOS26真机运行崩溃问题
flutter·app·swift
恋猫de小郭1 天前
对于普通程序员来说 AI 是什么?AI 究竟用的是什么?
前端·flutter·ai编程
卡尔特斯1 天前
Flutter A GlobalKey was used multipletimes inside one widget'schild list.The ...
flutter
w_y_fan1 天前
Flutter 滚动组件总结
前端·flutter
醉过才知酒浓1 天前
Flutter Getx 的页面传参
flutter
火柴就是我2 天前
flutter 之真手势冲突处理
android·flutter
Speed1232 天前
`mockito` 的核心“打桩”规则
flutter·dart