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),
                );
              },
            )),
      ),
    );
  }
}
相关推荐
程序员老刘6 小时前
跨平台开发地图:四月风暴前夕,你该怎么选?| 2026年4月
flutter·ai编程·客户端
MakeZero7 小时前
Flutter那些事-PageView
flutter
Lanren的编程日记9 小时前
Flutter鸿蒙应用开发:数据加密功能实现实战,全方位保护用户隐私数据
flutter·华为·harmonyos
梦想不只是梦与想10 小时前
flutter 与 Android iOS 通信?以及实现原理(一)
android·flutter·ios·methodchannel·eventchannel·basicmessage
2601_9495936512 小时前
Flutter OpenHarmony 三方库 path_provider 文件路径获取适配详解
flutter
哈撒Ki13 小时前
快速入门 Dart 语言
前端·flutter·dart
小蜜蜂嗡嗡13 小时前
flutter 自定义走马灯,内部为Widget控件的走马灯效果二:横向无限匀速滚动+每个Item与屏幕左侧对齐时,停靠3秒再继续滚动
开发语言·flutter
浮芷.13 小时前
生命科学数据视界防御:基于鸿蒙Flutter陀螺仪云台与三维体积光栅的视轴锁定架构
flutter·华为·架构·开源·harmonyos·鸿蒙
千码君201613 小时前
Flutter:在win10上第一次安装和尝试开发记录
flutter·gradle·android-studio·安卓模拟器
浮芷.13 小时前
微观搜打撤:基于鸿蒙flutter的内存快照算法的局内外状态隔离与高阶背包系统设计
算法·flutter·华为·开源·harmonyos·鸿蒙