【Flutter 实战】自定义动画-涟漪和雷达扫描


老孟导读:此篇文章是 Flutter 动画系列文章第五篇,本文介绍2个自定义动画:涟漪雷达扫描效果。web

涟漪

实现涟漪动画效果以下:canvas

此动画经过 CustomPainter 绘制配合 AnimationController 动画控制实现,定义动画控制部分:微信

class WaterRipple extends StatefulWidget { final int count; final Color color;
const WaterRipple({Key key, this.count = 3, this.color = const Color(0xFF0080ff)}) : super(key: key);
@override _WaterRippleState createState() => _WaterRippleState();}
class _WaterRippleState extends State<WaterRipple> with SingleTickerProviderStateMixin { AnimationController _controller;
@override void initState() { _controller = AnimationController(vsync: this, duration: Duration(milliseconds: 2000)) ..repeat(); super.initState(); }
@override void dispose() { _controller.dispose(); super.dispose(); }
@override Widget build(BuildContext context) { return AnimatedBuilder( animation: _controller, builder: (context, child) { return CustomPaint( painter: WaterRipplePainter(_controller.value,count: widget.count,color: widget.color), ); }, ); }}

countcolor 分别表明水波纹的数量和颜色。less

WaterRipplePainter 定义以下:编辑器

class WaterRipplePainter extends CustomPainter { final double progress; final int count; final Color color;
Paint _paint = Paint()..style = PaintingStyle.fill;
WaterRipplePainter(this.progress, {this.count = 3, this.color = const Color(0xFF0080ff)});
@override void paint(Canvas canvas, Size size) { double radius = min(size.width / 2, size.height / 2);
for (int i = count; i >= 0; i--) { final double opacity = (1.0 - ((i + progress) / (count + 1))); final Color _color = color.withOpacity(opacity); _paint..color = _color;
double _radius = radius * ((i + progress) / (count + 1));
canvas.drawCircle( Offset(size.width / 2, size.height / 2), _radius, _paint); } }
@override bool shouldRepaint(CustomPainter oldDelegate) { return true; }}

重点是 paint 方法,根据动画进度计算颜色的透明度和半径。ide

使用以下:flex

class WaterRipplePage extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( body: Center( child: Container(height: 200, width: 200, child: WaterRipple())), ); }}

雷达扫描

实现雷达扫描效果:动画

此效果分为两部分:中间的 logo 图片和扫描部分。ui

中间的 logo 图片

中间的 logo 图片边缘有阴影效果,像是太阳发光同样,实现:this

Container( height: 70.0, width: 70.0, decoration: BoxDecoration( color: Colors.grey, image: DecorationImage( image: AssetImage('assets/images/logo.png')), shape: BoxShape.circle, boxShadow: [ BoxShadow( color: Colors.white.withOpacity(.5), blurRadius: 5.0, spreadRadius: 3.0, ), ]),)

扫描

定义雷达扫描的动画控制器:

class RadarView extends StatefulWidget { @override _RadarViewState createState() => _RadarViewState();}
class _RadarViewState extends State<RadarView> with SingleTickerProviderStateMixin { AnimationController _controller; Animation<double> _animation;
@override void initState() { _controller = AnimationController(vsync: this, duration: Duration(seconds: 5)); _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 AnimatedBuilder( animation: _animation, builder: (context, child) { return CustomPaint( painter: RadarPainter(_animation.value), ); }, ); }}

RadarPainter 定义以下:

class RadarPainter extends CustomPainter { final double angle;
Paint _bgPaint = Paint() ..color = Colors.white ..strokeWidth = 1 ..style = PaintingStyle.stroke;
Paint _paint = Paint()..style = PaintingStyle.fill;
int circleCount = 3;
RadarPainter(this.angle);
@override void paint(Canvas canvas, Size size) { var radius = min(size.width / 2, size.height / 2);
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), [Colors.white.withOpacity(.01), Colors.white.withOpacity(.5)], [.0, 1.0], TileMode.clamp, .0, pi / 12);
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 / 12, true, _paint); canvas.restore(); }
@override bool shouldRepaint(CustomPainter oldDelegate) { return true; }}

将二者结合在一块儿:

class RadarPage extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( backgroundColor: Color(0xFF0F1532), body: Stack( children: [ Positioned.fill( left: 10, right: 10, child: Center( child: Stack(children: [ Positioned.fill( child: RadarView(), ), Positioned( child: Center( child: Container( height: 70.0, width: 70.0, decoration: BoxDecoration( color: Colors.grey, image: DecorationImage( image: AssetImage('assets/images/logo.png')), shape: BoxShape.circle, boxShadow: [ BoxShadow( color: Colors.white.withOpacity(.5), blurRadius: 5.0, spreadRadius: 3.0, ), ]), ), ), ), ]), ), ) ], )); }}



你可能还喜欢

关注「老孟Flutter」
让你天天进步一点点




本文分享自微信公众号 - 老孟Flutter(lao_meng_qd)。
若有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一块儿分享。

相关文章
相关标签/搜索