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